Initial Setup for the microbit v1
This commit is contained in:
42
.cargo/config.toml
Normal file
42
.cargo/config.toml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
[target.thumbv7m-none-eabi]
|
||||||
|
# uncomment this to make `cargo run` execute programs on QEMU
|
||||||
|
# runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel"
|
||||||
|
|
||||||
|
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||||
|
# TODO(2) replace `$CHIP` with your chip's name (see `probe-rs chip list` output)
|
||||||
|
runner = "probe-rs run --chip nRF51822_xxAA"
|
||||||
|
# runner = ["probe-rs", "run", "--chip", "nRF51822_xxAA", "--log-format", "{L} {s}"]
|
||||||
|
|
||||||
|
# uncomment ONE of these three option to make `cargo run` start a GDB session
|
||||||
|
# which option to pick depends on your system
|
||||||
|
#runner = "arm-none-eabi-gdb -q -x openocd.gdb"
|
||||||
|
# runner = "gdb-multiarch -q -x openocd.gdb"
|
||||||
|
# runner = "gdb -q -x openocd.gdb"
|
||||||
|
|
||||||
|
rustflags = [
|
||||||
|
# Previously, the linker arguments --nmagic and -Tlink.x were set here.
|
||||||
|
# They are now set by build.rs instead. The linker argument can still
|
||||||
|
# only be set here, if a custom linker is needed.
|
||||||
|
"-C", "linker=flip-link",
|
||||||
|
"-C", "link-arg=-Tdefmt.x",
|
||||||
|
# By default, the LLD linker is used, which is shipped with the Rust
|
||||||
|
# toolchain. If you run into problems with LLD, you can switch to the
|
||||||
|
# GNU linker by uncommenting this line:
|
||||||
|
# "-C", "linker=arm-none-eabi-ld",
|
||||||
|
|
||||||
|
# If you need to link to pre-compiled C libraries provided by a C toolchain
|
||||||
|
# use GCC as the linker by uncommenting the three lines below:
|
||||||
|
# "-C", "linker=arm-none-eabi-gcc",
|
||||||
|
# "-C", "link-arg=-Wl,-Tlink.x",
|
||||||
|
# "-C", "link-arg=-nostartfiles",
|
||||||
|
]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
# Pick ONE of these default compilation targets
|
||||||
|
target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
|
||||||
|
# target = "thumbv7m-none-eabi" # Cortex-M3
|
||||||
|
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
|
||||||
|
# target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
|
||||||
|
# target = "thumbv8m.base-none-eabi" # Cortex-M23
|
||||||
|
# target = "thumbv8m.main-none-eabi" # Cortex-M33 (no FPU)
|
||||||
|
# target = "thumbv8m.main-none-eabihf" # Cortex-M33 (with FPU)
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
15
.idea/customTargets.xml
generated
Normal file
15
.idea/customTargets.xml
generated
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CLionExternalBuildManager">
|
||||||
|
<target id="de437d0c-be58-4a32-a065-30cf5806e1cd" name="cargo-targets" defaultType="TOOL">
|
||||||
|
<configuration id="b18548c4-f93a-4480-8d5f-1e7226ad38bf" name="cargo-targets">
|
||||||
|
<build type="TOOL">
|
||||||
|
<tool actionId="Tool_External Tools_cargo-build" />
|
||||||
|
</build>
|
||||||
|
<clean type="TOOL">
|
||||||
|
<tool actionId="Tool_External Tools_cargo-clean" />
|
||||||
|
</clean>
|
||||||
|
</configuration>
|
||||||
|
</target>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/test-microbit.iml" filepath="$PROJECT_DIR$/.idea/test-microbit.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
22
.idea/runConfigurations/Build.xml
generated
Normal file
22
.idea/runConfigurations/Build.xml
generated
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Build" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
|
<option name="buildProfileId" value="dev" />
|
||||||
|
<option name="command" value="build --features v1" />
|
||||||
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
|
<envs>
|
||||||
|
<env name="DEFMT_LOG" value="trace" />
|
||||||
|
</envs>
|
||||||
|
<option name="emulateTerminal" value="true" />
|
||||||
|
<option name="channel" value="DEFAULT" />
|
||||||
|
<option name="requiredFeatures" value="true" />
|
||||||
|
<option name="allFeatures" value="false" />
|
||||||
|
<option name="withSudo" value="false" />
|
||||||
|
<option name="buildTarget" value="REMOTE" />
|
||||||
|
<option name="backtrace" value="SHORT" />
|
||||||
|
<option name="isRedirectInput" value="false" />
|
||||||
|
<option name="redirectInputPath" value="" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
21
.idea/runConfigurations/Embed.xml
generated
Normal file
21
.idea/runConfigurations/Embed.xml
generated
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Embed" type="CargoCommandRunConfiguration" factoryName="Cargo Command" nameIsGenerated="true">
|
||||||
|
<option name="command" value="embed --features v1" />
|
||||||
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
|
<envs>
|
||||||
|
<env name="DEFMT_LOG" value="trace" />
|
||||||
|
</envs>
|
||||||
|
<option name="emulateTerminal" value="true" />
|
||||||
|
<option name="channel" value="DEFAULT" />
|
||||||
|
<option name="requiredFeatures" value="true" />
|
||||||
|
<option name="allFeatures" value="false" />
|
||||||
|
<option name="withSudo" value="false" />
|
||||||
|
<option name="buildTarget" value="REMOTE" />
|
||||||
|
<option name="backtrace" value="SHORT" />
|
||||||
|
<option name="isRedirectInput" value="false" />
|
||||||
|
<option name="redirectInputPath" value="" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
21
.idea/runConfigurations/_template__of_Cargo_Command.xml
generated
Normal file
21
.idea/runConfigurations/_template__of_Cargo_Command.xml
generated
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="true" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
|
<option name="command" value="embed --features v1" />
|
||||||
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
|
<envs>
|
||||||
|
<env name="DEFMT_LOG" value="trace" />
|
||||||
|
</envs>
|
||||||
|
<option name="emulateTerminal" value="true" />
|
||||||
|
<option name="channel" value="DEFAULT" />
|
||||||
|
<option name="requiredFeatures" value="true" />
|
||||||
|
<option name="allFeatures" value="false" />
|
||||||
|
<option name="withSudo" value="false" />
|
||||||
|
<option name="buildTarget" value="REMOTE" />
|
||||||
|
<option name="backtrace" value="SHORT" />
|
||||||
|
<option name="isRedirectInput" value="false" />
|
||||||
|
<option name="redirectInputPath" value="" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
10
.idea/runConfigurations/_template__of_com_jetbrains_cidr_embedded_openocd_conf_factory.xml
generated
Normal file
10
.idea/runConfigurations/_template__of_com_jetbrains_cidr_embedded_openocd_conf_factory.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="true" type="com.jetbrains.cidr.embedded.openocd.conf.type" factoryName="com.jetbrains.cidr.embedded.openocd.conf.factory" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="template-microbit" TARGET_NAME="cargo-targets" CONFIG_NAME="cargo-targets" version="1" RUN_PATH="$PROJECT_DIR$/target/thumbv6m-none-eabi/debug/template-microbit">
|
||||||
|
<openocd version="1" gdb-port="3333" telnet-port="4444" board-config="$PROJECT_DIR$/openocd.cfg" reset-type="INIT" download-type="UPDATED_ONLY">
|
||||||
|
<debugger kind="GDB" isBundled="true" />
|
||||||
|
</openocd>
|
||||||
|
<method v="2">
|
||||||
|
<option name="CLION.COMPOUND.BUILD" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
13
.idea/test-microbit.iml
generated
Normal file
13
.idea/test-microbit.iml
generated
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="CPP_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
23
.idea/tools/External Tools.xml
generated
Normal file
23
.idea/tools/External Tools.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<toolSet name="External Tools">
|
||||||
|
<tool name="cargo-build" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true">
|
||||||
|
<exec>
|
||||||
|
<option name="COMMAND" value="cargo" />
|
||||||
|
<option name="PARAMETERS" value="build" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
|
||||||
|
</exec>
|
||||||
|
</tool>
|
||||||
|
<tool name="cargo-build-release" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true">
|
||||||
|
<exec>
|
||||||
|
<option name="COMMAND" value="cargo" />
|
||||||
|
<option name="PARAMETERS" value="build --release" />
|
||||||
|
<option name="WORKING_DIRECTORY" />
|
||||||
|
</exec>
|
||||||
|
</tool>
|
||||||
|
<tool name="cargo-clean" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true">
|
||||||
|
<exec>
|
||||||
|
<option name="COMMAND" value="cargo" />
|
||||||
|
<option name="PARAMETERS" value="clean" />
|
||||||
|
<option name="WORKING_DIRECTORY" />
|
||||||
|
</exec>
|
||||||
|
</tool>
|
||||||
|
</toolSet>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
9
.vscode/settings.json
vendored
Normal file
9
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
// override the default setting (`cargo check --all-targets`) which produces the following error
|
||||||
|
// "can't find crate for `test`" when the default compilation target is a no_std target
|
||||||
|
// with these changes RA will call `cargo check --bins` on save
|
||||||
|
"rust-analyzer.checkOnSave.allTargets": false,
|
||||||
|
"rust-analyzer.checkOnSave.extraArgs": [
|
||||||
|
"--bins"
|
||||||
|
]
|
||||||
|
}
|
||||||
84
Cargo.toml
Normal file
84
Cargo.toml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
[package]
|
||||||
|
authors = ["Lionel Sambuc <lionel.sambuc@gmail.com>"]
|
||||||
|
name = "template-microbit"
|
||||||
|
edition = "2021"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
# needed for each integration test
|
||||||
|
[[test]]
|
||||||
|
name = "integration"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[dependencies.microbit-v2]
|
||||||
|
version = "0.15.1"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.microbit]
|
||||||
|
version = "0.15.1"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cortex-m = { version = "0.7", features = ["critical-section-single-core", "inline-asm"] }
|
||||||
|
cortex-m-rt = "0.7"
|
||||||
|
#cortex-m-semihosting = "0.5.0"
|
||||||
|
#panic-halt = "0.2.0"
|
||||||
|
panic-rtt-target = "0.1"
|
||||||
|
|
||||||
|
defmt = "0.3"
|
||||||
|
# Either rtt-target or defmt-rtt
|
||||||
|
#rtt-target = "0.5"
|
||||||
|
defmt-rtt = "0.4"
|
||||||
|
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
||||||
|
|
||||||
|
embedded-hal = "1.0.0"
|
||||||
|
embedded-alloc = "0.6.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
defmt-test = "0.3"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["v1"]
|
||||||
|
v2 = ["microbit-v2"]
|
||||||
|
v1 = ["microbit"]
|
||||||
|
|
||||||
|
# cargo build/run
|
||||||
|
[profile.dev]
|
||||||
|
codegen-units = 1
|
||||||
|
debug = 2
|
||||||
|
debug-assertions = true # <-
|
||||||
|
incremental = false
|
||||||
|
opt-level = 'z' # <-
|
||||||
|
overflow-checks = true # <-
|
||||||
|
|
||||||
|
# cargo test
|
||||||
|
[profile.test]
|
||||||
|
codegen-units = 1
|
||||||
|
debug = 2
|
||||||
|
debug-assertions = true # <-
|
||||||
|
incremental = false
|
||||||
|
opt-level = 3 # <-
|
||||||
|
overflow-checks = true # <-
|
||||||
|
|
||||||
|
# cargo build/run --release
|
||||||
|
[profile.release]
|
||||||
|
codegen-units = 1
|
||||||
|
debug = 2
|
||||||
|
debug-assertions = false # <-
|
||||||
|
incremental = false
|
||||||
|
lto = 'fat'
|
||||||
|
opt-level = 3 # <-
|
||||||
|
overflow-checks = false # <-
|
||||||
|
|
||||||
|
# cargo test --release
|
||||||
|
[profile.bench]
|
||||||
|
codegen-units = 1
|
||||||
|
debug = 2
|
||||||
|
debug-assertions = false # <-
|
||||||
|
incremental = false
|
||||||
|
lto = 'fat'
|
||||||
|
opt-level = 3 # <-
|
||||||
|
overflow-checks = false # <-
|
||||||
|
|
||||||
12
Embed.toml
Normal file
12
Embed.toml
Normal file
@@ -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 = true
|
||||||
|
|
||||||
|
[default.gdb]
|
||||||
|
enabled = false
|
||||||
23
LICENSE-MIT
Normal file
23
LICENSE-MIT
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
67
README.md
Normal file
67
README.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# Template for `BBC micro:bit`
|
||||||
|
|
||||||
|
This template as been setup for the BBC micro:bit.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
You are now all set to `cargo-run` your first `defmt`-powered application!
|
||||||
|
There are some examples in the `src/bin` directory.
|
||||||
|
|
||||||
|
Start by `cargo run`-ning `my-app/src/bin/hello.rs`:
|
||||||
|
|
||||||
|
``` console
|
||||||
|
$ # `rb` is an alias for `run --bin`
|
||||||
|
$ cargo rb hello
|
||||||
|
Finished dev [optimized + debuginfo] target(s) in 0.03s
|
||||||
|
flashing program ..
|
||||||
|
DONE
|
||||||
|
resetting device
|
||||||
|
0.000000 INFO Hello, world!
|
||||||
|
(..)
|
||||||
|
|
||||||
|
$ echo $?
|
||||||
|
0
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're running out of memory (`flip-link` bails with an overflow error), you can decrease the size of the device memory buffer by setting the `DEFMT_RTT_BUFFER_SIZE` environment variable. The default value is 1024 bytes, and powers of two should be used for optimal performance:
|
||||||
|
|
||||||
|
``` console
|
||||||
|
$ DEFMT_RTT_BUFFER_SIZE=64 cargo rb hello
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
|
||||||
|
The template comes configured for running unit tests and integration tests on the target.
|
||||||
|
|
||||||
|
Unit tests reside in the library crate and can test private API; the initial set of unit tests are in `src/lib.rs`.
|
||||||
|
`cargo test --lib` will run those unit tests.
|
||||||
|
|
||||||
|
``` console
|
||||||
|
$ cargo test --lib
|
||||||
|
(1/1) running `it_works`...
|
||||||
|
└─ app::unit_tests::__defmt_test_entry @ src/lib.rs:33
|
||||||
|
all tests passed!
|
||||||
|
└─ app::unit_tests::__defmt_test_entry @ src/lib.rs:28
|
||||||
|
```
|
||||||
|
|
||||||
|
Integration tests reside in the `tests` directory; the initial set of integration tests are in `tests/integration.rs`.
|
||||||
|
`cargo test --test integration` will run those integration tests.
|
||||||
|
Note that the argument of the `--test` flag must match the name of the test file in the `tests` directory.
|
||||||
|
|
||||||
|
``` console
|
||||||
|
$ cargo test --test integration
|
||||||
|
(1/1) running `it_works`...
|
||||||
|
└─ integration::tests::__defmt_test_entry @ tests/integration.rs:13
|
||||||
|
all tests passed!
|
||||||
|
└─ integration::tests::__defmt_test_entry @ tests/integration.rs:8
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that to add a new test file to the `tests` directory you also need to add a new `[[test]]` section to `Cargo.toml`.
|
||||||
|
|
||||||
|
## Generated using app-template
|
||||||
|
|
||||||
|
`app-template` is part of the [Knurling] project, [Ferrous Systems]' effort at
|
||||||
|
improving tooling used to develop for embedded systems.
|
||||||
|
|
||||||
|
[Knurling]: https://knurling.ferrous-systems.com
|
||||||
|
[Ferrous Systems]: https://ferrous-systems.com/
|
||||||
29
SETUP.md
Normal file
29
SETUP.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Install tools
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo install cargo-binutils
|
||||||
|
cargo install probe-rs-tools
|
||||||
|
rustup component add llvm-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
## MacOS
|
||||||
|
|
||||||
|
```shell
|
||||||
|
brew install arm-none-eabi-gdb picocom lsusb
|
||||||
|
```
|
||||||
|
|
||||||
|
## Other OSes
|
||||||
|
|
||||||
|
Please refer to the instructions from:
|
||||||
|
|
||||||
|
* Linux : https://docs.rust-embedded.org/discovery/microbit/03-setup/linux.html
|
||||||
|
* Windows : https://docs.rust-embedded.org/discovery/microbit/03-setup/windows.html
|
||||||
|
|
||||||
|
# Verifying installation
|
||||||
|
|
||||||
|
With the BBC micro:bit connected through USB:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
probe-rs list
|
||||||
|
probe-rs info
|
||||||
|
```
|
||||||
43
build.rs
Normal file
43
build.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
//! 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,
|
||||||
|
//! updating `memory.x` ensures a rebuild of the application with the
|
||||||
|
//! new memory settings.
|
||||||
|
//!
|
||||||
|
//! The build script also sets the linker flags to tell it which link script to use.
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
// Specify linker arguments.
|
||||||
|
|
||||||
|
// `--nmagic` is required if memory section addresses are not aligned to 0x10000,
|
||||||
|
// for example the FLASH and RAM sections in your `memory.x`.
|
||||||
|
// See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
|
||||||
|
println!("cargo:rustc-link-arg=--nmagic");
|
||||||
|
|
||||||
|
// Set the linker script to the one provided by cortex-m-rt.
|
||||||
|
println!("cargo:rustc-link-arg=-Tlink.x");
|
||||||
|
}
|
||||||
13
examples/bitfield.rs
Normal file
13
examples/bitfield.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use template_microbit as _; // global logger + panicking-behavior + memory layout
|
||||||
|
|
||||||
|
#[cortex_m_rt::entry]
|
||||||
|
fn main() -> ! {
|
||||||
|
// value of the FREQUENCY register (nRF52840 device; RADIO peripheral)
|
||||||
|
let frequency: u32 = 276;
|
||||||
|
defmt::info!("FREQUENCY: {0=0..7}, MAP: {0=8..9}", frequency);
|
||||||
|
|
||||||
|
template_microbit::exit()
|
||||||
|
}
|
||||||
29
examples/format.rs
Normal file
29
examples/format.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use template_microbit as _; // global logger + panicking-behavior + memory layout
|
||||||
|
use defmt::Format; // <- derive attribute
|
||||||
|
|
||||||
|
#[derive(Format)]
|
||||||
|
struct S1<T> {
|
||||||
|
x: u8,
|
||||||
|
y: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Format)]
|
||||||
|
struct S2 {
|
||||||
|
z: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cortex_m_rt::entry]
|
||||||
|
fn main() -> ! {
|
||||||
|
let s = S1 {
|
||||||
|
x: 42,
|
||||||
|
y: S2 { z: 43 },
|
||||||
|
};
|
||||||
|
defmt::println!("s={:?}", s);
|
||||||
|
let x = 42;
|
||||||
|
defmt::println!("x={=u8}", x);
|
||||||
|
|
||||||
|
template_microbit::exit()
|
||||||
|
}
|
||||||
18
examples/hello.rs
Normal file
18
examples/hello.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
// global logger + panicking-behavior + memory layout
|
||||||
|
|
||||||
|
use microbit::board::Board;
|
||||||
|
use microbit::hal::gpio::Level::High;
|
||||||
|
|
||||||
|
#[cortex_m_rt::entry]
|
||||||
|
fn main() -> ! {
|
||||||
|
let board = Board::take().unwrap();
|
||||||
|
board.display_pins.col1.into_pulldown_input();
|
||||||
|
board.display_pins.row1.into_push_pull_output(High);
|
||||||
|
|
||||||
|
defmt::println!("Hello, world! freq ", );
|
||||||
|
|
||||||
|
template_microbit::exit()
|
||||||
|
}
|
||||||
20
examples/levels.rs
Normal file
20
examples/levels.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use template_microbit as _;
|
||||||
|
use template_microbit::exit;
|
||||||
|
// global logger + panicking-behavior + memory layout
|
||||||
|
|
||||||
|
#[cortex_m_rt::entry]
|
||||||
|
fn main() -> ! {
|
||||||
|
// try setting the DEFMT_LOG environment variable
|
||||||
|
// e.g. `export DEFMT_LOG=info` or `DEFMT_LOG=trace cargo rb levels`
|
||||||
|
defmt::info!("info");
|
||||||
|
defmt::trace!("trace");
|
||||||
|
defmt::warn!("warn");
|
||||||
|
defmt::debug!("debug");
|
||||||
|
defmt::error!("error");
|
||||||
|
exit();
|
||||||
|
|
||||||
|
template_microbit::exit()
|
||||||
|
}
|
||||||
28
examples/overflow.rs
Normal file
28
examples/overflow.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use template_microbit as _; // global logger + panicking-behavior + memory layout
|
||||||
|
|
||||||
|
#[cortex_m_rt::entry]
|
||||||
|
fn main() -> ! {
|
||||||
|
ack(10, 10);
|
||||||
|
template_microbit::exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ack(m: u32, n: u32) -> u32 {
|
||||||
|
// waste stack space to trigger a stack overflow
|
||||||
|
let mut buffer = [0u8; 16 * 1024];
|
||||||
|
// estimate of the Stack Pointer register
|
||||||
|
let sp = buffer.as_mut_ptr();
|
||||||
|
defmt::println!("ack(m={=u32}, n={=u32}, SP={:x})", m, n, sp);
|
||||||
|
|
||||||
|
if m == 0 {
|
||||||
|
n + 1
|
||||||
|
} else {
|
||||||
|
if n == 0 {
|
||||||
|
ack(m - 1, 1)
|
||||||
|
} else {
|
||||||
|
ack(m - 1, ack(m, n - 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
examples/panic.rs
Normal file
11
examples/panic.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use template_microbit as _; // global logger + panicking-behavior + memory layout
|
||||||
|
|
||||||
|
#[cortex_m_rt::entry]
|
||||||
|
fn main() -> ! {
|
||||||
|
defmt::println!("main");
|
||||||
|
|
||||||
|
defmt::panic!()
|
||||||
|
}
|
||||||
11
memory.x
Normal file
11
memory.x
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
/* NOTE K = KiBi = 1024 bytes */
|
||||||
|
FLASH : ORIGIN = 0x00000000, LENGTH = 256K
|
||||||
|
RAM : ORIGIN = 0x20000000, LENGTH = 16K
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is where the call stack will be allocated. */
|
||||||
|
/* The stack is of the full descending type. */
|
||||||
|
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
||||||
|
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
||||||
2
openocd.cfg
Normal file
2
openocd.cfg
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
source [find interface/cmsis-dap.cfg]
|
||||||
|
source [find target/nrf51.cfg]
|
||||||
40
openocd.gdb
Normal file
40
openocd.gdb
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
target extended-remote :3333
|
||||||
|
|
||||||
|
# print demangled symbols
|
||||||
|
set print asm-demangle on
|
||||||
|
|
||||||
|
# set backtrace limit to not have infinite backtrace loops
|
||||||
|
set backtrace limit 32
|
||||||
|
|
||||||
|
# detect unhandled exceptions, hard faults and panics
|
||||||
|
break DefaultHandler
|
||||||
|
break HardFault
|
||||||
|
break rust_begin_unwind
|
||||||
|
# # run the next few lines so the panic message is printed immediately
|
||||||
|
# # the number needs to be adjusted for your panic handler
|
||||||
|
# commands $bpnum
|
||||||
|
# next 4
|
||||||
|
# end
|
||||||
|
|
||||||
|
# *try* to stop at the user entry point (it might be gone due to inlining)
|
||||||
|
break main
|
||||||
|
|
||||||
|
monitor arm semihosting enable
|
||||||
|
|
||||||
|
# # send captured ITM to the file itm.fifo
|
||||||
|
# # (the microcontroller SWO pin must be connected to the programmer SWO pin)
|
||||||
|
# # 8000000 must match the core clock frequency
|
||||||
|
# monitor tpiu config internal itm.txt uart off 8000000
|
||||||
|
|
||||||
|
# # OR: make the microcontroller SWO pin output compatible with UART (8N1)
|
||||||
|
# # 8000000 must match the core clock frequency
|
||||||
|
# # 2000000 is the frequency of the SWO pin
|
||||||
|
# monitor tpiu config external uart off 8000000 2000000
|
||||||
|
|
||||||
|
# # enable ITM port 0
|
||||||
|
# monitor itm port 0 on
|
||||||
|
|
||||||
|
load
|
||||||
|
|
||||||
|
# start the process but immediately halt the processor
|
||||||
|
stepi
|
||||||
3
rust-toolchain.toml
Normal file
3
rust-toolchain.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "1.82.0"
|
||||||
|
components = ["rustfmt", "llvm-tools-preview"]
|
||||||
7
src/bit_string/mod.rs
Normal file
7
src/bit_string/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mod thumbv6m;
|
||||||
|
|
||||||
|
pub trait Sender {
|
||||||
|
fn send(&self, value: u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use thumbv6m::Msb2LsbSender;
|
||||||
161
src/bit_string/thumbv6m.rs
Normal file
161
src/bit_string/thumbv6m.rs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
use super::Sender;
|
||||||
|
use core::arch::asm;
|
||||||
|
|
||||||
|
pub struct Msb2LsbSender<
|
||||||
|
const BITS: u32,
|
||||||
|
const SHIFT: u32,
|
||||||
|
const PIN_MASK: u32,
|
||||||
|
const PIN_LEVEL_A_ADDRESS: u32,
|
||||||
|
const PIN_LEVEL_B_ADDRESS: u32,
|
||||||
|
const T0A: u32,
|
||||||
|
const T0B: u32,
|
||||||
|
const T1A: u32,
|
||||||
|
const T1B: u32,
|
||||||
|
> {}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
const BITS: u32,
|
||||||
|
const SHIFT: u32,
|
||||||
|
const PIN_MASK: u32,
|
||||||
|
const PIN_LEVEL_A_ADDRESS: u32,
|
||||||
|
const PIN_LEVEL_B_ADDRESS: u32,
|
||||||
|
const T0A: u32,
|
||||||
|
const T0B: u32,
|
||||||
|
const T1A: u32,
|
||||||
|
const T1B: u32,
|
||||||
|
>
|
||||||
|
Msb2LsbSender<
|
||||||
|
BITS,
|
||||||
|
SHIFT,
|
||||||
|
PIN_MASK,
|
||||||
|
PIN_LEVEL_A_ADDRESS,
|
||||||
|
PIN_LEVEL_B_ADDRESS,
|
||||||
|
T0A,
|
||||||
|
T0B,
|
||||||
|
T1A,
|
||||||
|
T1B,
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Msb2LsbSender {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
const BITS: u32,
|
||||||
|
const SHIFT: u32,
|
||||||
|
const PIN_MASK: u32,
|
||||||
|
const PIN_LEVEL_A_ADDRESS: u32,
|
||||||
|
const PIN_LEVEL_B_ADDRESS: u32,
|
||||||
|
const T0A: u32,
|
||||||
|
const T0B: u32,
|
||||||
|
const T1A: u32,
|
||||||
|
const T1B: u32,
|
||||||
|
> Sender
|
||||||
|
for Msb2LsbSender<
|
||||||
|
BITS,
|
||||||
|
SHIFT,
|
||||||
|
PIN_MASK,
|
||||||
|
PIN_LEVEL_A_ADDRESS,
|
||||||
|
PIN_LEVEL_B_ADDRESS,
|
||||||
|
T0A,
|
||||||
|
T0B,
|
||||||
|
T1A,
|
||||||
|
T1B,
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// To send
|
||||||
|
// Send a string of bit, stored in an u32. first shift by SHIFT to place the first bit in the MSB
|
||||||
|
// position, then transmit BITS bits, from the most to the least significant one.
|
||||||
|
// PIN_MASK is the mask to use to write in PIN_START_ADDRESS, and PIN_STOP_ADDRESS to toggle the pin.
|
||||||
|
// T0A, T0B, T1A, T1B are the number of cycles to wait after writing to A START, and B STOP address.
|
||||||
|
// At a minimum there is always a 3cycle-delay, and due to rounding there might be a +/- 1 cycle
|
||||||
|
// difference for non-multiple of 3.
|
||||||
|
fn send(&self, value: u32) {
|
||||||
|
unsafe {
|
||||||
|
let mut primask: u32;
|
||||||
|
asm!(
|
||||||
|
// Save interrupt mask and disable interrupts for each bit
|
||||||
|
// We do this outside of the bit sending loop as it allows callers of the function to tune
|
||||||
|
// how long the interrupts are disabled by setting the BITS type constant.
|
||||||
|
" mrs {primask}, PRIMASK",
|
||||||
|
" movs {tmp}, #1",
|
||||||
|
" orrs {tmp}, {primask}",
|
||||||
|
" msr PRIMASK, {tmp}",
|
||||||
|
primask = out(reg) primask,
|
||||||
|
tmp = out(reg) _,
|
||||||
|
);
|
||||||
|
|
||||||
|
asm!(
|
||||||
|
"3:",
|
||||||
|
// reload pin mask
|
||||||
|
" movs {tmp}, #{pin_mask}",
|
||||||
|
// shift left and update carry flag
|
||||||
|
" lsls {value}, {value}, #1",
|
||||||
|
// send a 0 or a 1 based on the carry flag value
|
||||||
|
" bcs 1f",
|
||||||
|
" nop",
|
||||||
|
" movs {delay1}, #{t0a}",
|
||||||
|
" movs {delay2}, #{t0b}",
|
||||||
|
" b 2f",
|
||||||
|
"1: ",
|
||||||
|
" movs {delay1}, #{t1a}",
|
||||||
|
" movs {delay2}, #{t1b}",
|
||||||
|
" b 2f",
|
||||||
|
"2: ",
|
||||||
|
|
||||||
|
// set or clear pin
|
||||||
|
" str {tmp}, [{pin_start_address}]",
|
||||||
|
// wait first delay
|
||||||
|
"1:",
|
||||||
|
" subs {delay1}, #1",
|
||||||
|
" bgt 1b",
|
||||||
|
// toggle pin
|
||||||
|
" str {tmp}, [{pin_stop_address}]",
|
||||||
|
// wait second delay
|
||||||
|
"1:",
|
||||||
|
" subs {delay2}, #1",
|
||||||
|
" bgt 1b",
|
||||||
|
|
||||||
|
// loop back to send next bit
|
||||||
|
" subs {bit}, #1",
|
||||||
|
" bgt 3b",
|
||||||
|
|
||||||
|
// A loop is 3 cycles, and the index is predecremented, so make sure it is at least 1.
|
||||||
|
// We also add 1 so that the integer division is at most off by one.
|
||||||
|
t0a = const match T0A {
|
||||||
|
0..3 => 1,
|
||||||
|
_ => (T0A + 1) / 3,
|
||||||
|
},
|
||||||
|
t0b = const match T0B {
|
||||||
|
0..3 => 1,
|
||||||
|
_ => (T0A + 1) / 3,
|
||||||
|
},
|
||||||
|
t1a = const match T1A {
|
||||||
|
0..3 => 1,
|
||||||
|
_ => (T1A + 1) / 3,
|
||||||
|
},
|
||||||
|
t1b = const match T1B {
|
||||||
|
0..3 => 1,
|
||||||
|
_ => (T1B + 1) / 3,
|
||||||
|
},
|
||||||
|
pin_mask = const PIN_MASK,
|
||||||
|
pin_start_address = in(reg) PIN_LEVEL_A_ADDRESS,
|
||||||
|
pin_stop_address = in(reg) PIN_LEVEL_B_ADDRESS,
|
||||||
|
// Number of bits to transmit
|
||||||
|
bit = inout(reg) BITS => _,
|
||||||
|
// Place the value in the most significant bits of the register
|
||||||
|
value = inout(reg) value << SHIFT => _,
|
||||||
|
delay1 = out(reg) _,
|
||||||
|
delay2 = out(reg) _,
|
||||||
|
tmp = out(reg) _,
|
||||||
|
);
|
||||||
|
|
||||||
|
asm!(
|
||||||
|
// Restore primask to previous state.
|
||||||
|
" msr PRIMASK, {primask}",
|
||||||
|
primask = in(reg) primask,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/led_display/mod.rs
Normal file
22
src/led_display/mod.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
pub trait PixelDisplay<Position> {
|
||||||
|
fn show(&self);
|
||||||
|
|
||||||
|
fn clear(&mut self);
|
||||||
|
|
||||||
|
fn fill(&mut self, value: u32);
|
||||||
|
|
||||||
|
fn point(&mut self, position: Position, value: u32);
|
||||||
|
|
||||||
|
fn fill_rectangle(&mut self, a: Position, b: Position, value: u32);
|
||||||
|
|
||||||
|
fn fill_circle(&mut self, origin: Position, radius: usize, value: u32);
|
||||||
|
|
||||||
|
fn rectangle(&mut self, bottom_left: Position, upper_right: Position, value: u32);
|
||||||
|
|
||||||
|
fn circle(&mut self, origin: Position, radius: usize, value: u32);
|
||||||
|
|
||||||
|
// fn segment(&mut self, origin: Position, end: Position, value: u32);
|
||||||
|
//
|
||||||
|
// fn copy(&mut self, origin: Position, data: &[[u32]]);
|
||||||
|
|
||||||
|
}
|
||||||
52
src/lib.rs
Normal file
52
src/lib.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// #![no_main]
|
||||||
|
#![no_std]
|
||||||
|
//
|
||||||
|
// use cortex_m_semihosting::debug;
|
||||||
|
//
|
||||||
|
// use defmt_rtt as _; // global logger
|
||||||
|
//
|
||||||
|
// // use some_hal as _; // memory layout
|
||||||
|
// use microbit as _;
|
||||||
|
//
|
||||||
|
// use panic_probe as _;
|
||||||
|
//
|
||||||
|
// // same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
||||||
|
// // this prevents the panic message being printed *twice* when `defmt::panic` is invoked
|
||||||
|
// #[defmt::panic_handler]
|
||||||
|
// fn panic() -> ! {
|
||||||
|
// cortex_m::asm::udf()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// Terminates the application and makes a semihosting-capable debug tool exit
|
||||||
|
// /// with status code 0.
|
||||||
|
// pub fn exit() -> ! {
|
||||||
|
// loop {
|
||||||
|
// debug::exit(debug::EXIT_SUCCESS);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// Hardfault handler.
|
||||||
|
// ///
|
||||||
|
// /// Terminates the application and makes a semihosting-capable debug tool exit
|
||||||
|
// /// with an error. This seems better than the default, which is to spin in a
|
||||||
|
// /// loop.
|
||||||
|
// #[cortex_m_rt::exception]
|
||||||
|
// unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
|
||||||
|
// loop {
|
||||||
|
// debug::exit(debug::EXIT_FAILURE);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
|
||||||
|
// // once within a crate. the module can be in any file but there can only be at most
|
||||||
|
// // one `#[tests]` module in this library crate
|
||||||
|
// #[cfg(test)]
|
||||||
|
// #[defmt_test::tests]
|
||||||
|
// mod unit_tests {
|
||||||
|
// use defmt::assert;
|
||||||
|
//
|
||||||
|
// #[test]
|
||||||
|
// fn it_works() {
|
||||||
|
// assert!(true)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
93
src/main.rs
Normal file
93
src/main.rs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use crate::neo_pixel::CompositeNeoPixelDisplay;
|
||||||
|
use cortex_m::asm;
|
||||||
|
use cortex_m::asm::delay;
|
||||||
|
use defmt_rtt as _;
|
||||||
|
use led_display::PixelDisplay;
|
||||||
|
use microbit::hal::gpio::Level::Low;
|
||||||
|
use microbit::Board;
|
||||||
|
use panic_probe as _;
|
||||||
|
|
||||||
|
mod bit_string;
|
||||||
|
mod led_display;
|
||||||
|
mod neo_pixel;
|
||||||
|
|
||||||
|
// Bit delay, starting always from set HIGH, wait, go to LOW, wait, repeat
|
||||||
|
const T0H_NS: u32 = 250; // 350+/-150 [ns]
|
||||||
|
const T1H_NS: u32 = 900; // 700+/-150 [ns]
|
||||||
|
|
||||||
|
// Latch reset delay
|
||||||
|
const TLL_NS: u32 = 250_000; // 6_000 [ns] min, newer pixels require ~250_000 [ns]
|
||||||
|
|
||||||
|
const T0L_NS: u32 = 900; // 800+/-150 [ns]
|
||||||
|
const T1L_NS: u32 = 600; // 600+/-150 [ns]
|
||||||
|
|
||||||
|
// Rounded down, from 20.8333
|
||||||
|
const NS_PER_CYCLE: u32 = 114; // empirically measured to be about 114ns
|
||||||
|
|
||||||
|
const T0H: u32 = T0H_NS / NS_PER_CYCLE;
|
||||||
|
const T0L: u32 = T0L_NS / NS_PER_CYCLE;
|
||||||
|
const T1H: u32 = T1H_NS / NS_PER_CYCLE;
|
||||||
|
const T1L: u32 = T1L_NS / NS_PER_CYCLE;
|
||||||
|
const TLL: u32 = TLL_NS / NS_PER_CYCLE;
|
||||||
|
|
||||||
|
// TODO RETRIEVE THIS FROM PAC
|
||||||
|
const EDGE_PAD0_MASK: u32 = 1 << 3;
|
||||||
|
const EDGE_PAD0_SET_ADDRESS: u32 = 0x5000_0508; // Address of the SET register
|
||||||
|
const EDGE_PAD0_CLEAR_ADDRESS: u32 = 0x5000_050c; // Address of the toggle register
|
||||||
|
|
||||||
|
fn setup() -> CompositeNeoPixelDisplay<8, 8, 4, 1, 2192, 8, 1342178568, 1342178572, 2, 7, 7, 5> {
|
||||||
|
let board = Board::take().unwrap();
|
||||||
|
let _pin = board.edge.e00.into_push_pull_output(Low);
|
||||||
|
delay(TLL * 2);
|
||||||
|
|
||||||
|
CompositeNeoPixelDisplay::<
|
||||||
|
8,
|
||||||
|
8,
|
||||||
|
4, /*NX*/
|
||||||
|
1, /*NY*/
|
||||||
|
TLL,
|
||||||
|
EDGE_PAD0_MASK,
|
||||||
|
EDGE_PAD0_SET_ADDRESS,
|
||||||
|
EDGE_PAD0_CLEAR_ADDRESS,
|
||||||
|
T0H,
|
||||||
|
T0L,
|
||||||
|
T1H,
|
||||||
|
T1L,
|
||||||
|
>::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cortex_m_rt::entry]
|
||||||
|
fn main() -> ! {
|
||||||
|
const SPACING: usize = 32;
|
||||||
|
let mut display = setup();
|
||||||
|
let mut frame = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
display.clear();
|
||||||
|
|
||||||
|
display.vbar_shift_left(0x020000, frame, SPACING);
|
||||||
|
display.vbar_shift_left(0x020000, frame + 1, SPACING);
|
||||||
|
display.vbar_shift_left(0x020000, frame + 2, SPACING);
|
||||||
|
display.vbar_shift_left(0x000200, frame + 3, SPACING);
|
||||||
|
display.vbar_shift_left(0x000200, frame + 4, SPACING);
|
||||||
|
display.vbar_shift_left(0x000002, frame + 5, SPACING);
|
||||||
|
display.fill_circle(
|
||||||
|
display.position((frame + 32 - 4) % { 4 * 8 }, 3),
|
||||||
|
2,
|
||||||
|
0x020000,
|
||||||
|
);
|
||||||
|
display.circle(
|
||||||
|
display.position((frame + 32 - 4) % { 4 * 8 }, 3),
|
||||||
|
3,
|
||||||
|
0x040000,
|
||||||
|
);
|
||||||
|
|
||||||
|
display.show();
|
||||||
|
frame += 1;
|
||||||
|
|
||||||
|
asm::delay(200_000); // TODO: REPLACE WITH TIMER DELAY
|
||||||
|
}
|
||||||
|
}
|
||||||
358
src/neo_pixel/mod.rs
Normal file
358
src/neo_pixel/mod.rs
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
use crate::bit_string;
|
||||||
|
use crate::bit_string::Sender;
|
||||||
|
use crate::led_display::PixelDisplay;
|
||||||
|
use cortex_m::asm;
|
||||||
|
use defmt::{warn, Format};
|
||||||
|
|
||||||
|
#[derive(Eq, Format, Ord, PartialEq, PartialOrd)]
|
||||||
|
pub struct Position {
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Position {
|
||||||
|
fn new(x: usize, y: usize) -> Self {
|
||||||
|
Self { x, y }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type NeoPixelSender<
|
||||||
|
const PIN_MASK: u32,
|
||||||
|
const PIN_LEVEL_HIGH_ADDRESS: u32,
|
||||||
|
const PIN_LEVEL_LOW_ADDRESS: u32,
|
||||||
|
const DELAY_ZERO_HIGH: u32,
|
||||||
|
const DELAY_ZERO_LOW: u32,
|
||||||
|
const DELAY_ONE_HIGH: u32,
|
||||||
|
const DELAY_ONE_LOW: u32,
|
||||||
|
> = bit_string::Msb2LsbSender<
|
||||||
|
24,
|
||||||
|
8,
|
||||||
|
PIN_MASK,
|
||||||
|
PIN_LEVEL_HIGH_ADDRESS,
|
||||||
|
PIN_LEVEL_LOW_ADDRESS,
|
||||||
|
DELAY_ZERO_HIGH,
|
||||||
|
DELAY_ZERO_LOW,
|
||||||
|
DELAY_ONE_HIGH,
|
||||||
|
DELAY_ONE_LOW,
|
||||||
|
>;
|
||||||
|
|
||||||
|
struct BufferPosition<const X: usize, const Y: usize, const NX: usize, const NY: usize> {
|
||||||
|
row: usize,
|
||||||
|
col: usize,
|
||||||
|
y: usize,
|
||||||
|
x: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const X: usize, const Y: usize, const NX: usize, const NY: usize> TryFrom<Option<Position>>
|
||||||
|
for BufferPosition<X, Y, NX, NY>
|
||||||
|
{
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(position: Option<Position>) -> Result<Self, Self::Error> {
|
||||||
|
if let Some(position) = position {
|
||||||
|
position.try_into()
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const X: usize, const Y: usize, const NX: usize, const NY: usize> TryFrom<Position>
|
||||||
|
for BufferPosition<X, Y, NX, NY>
|
||||||
|
{
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(position: Position) -> Result<Self, Self::Error> {
|
||||||
|
let col = position.x / X;
|
||||||
|
let row = position.y / Y;
|
||||||
|
|
||||||
|
if col > NX && row > NY {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
if col > NX {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
if row > NY {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = position.x % X;
|
||||||
|
let y = position.y % Y;
|
||||||
|
Ok(BufferPosition { row, col, y, x })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CompositeNeoPixelDisplay<
|
||||||
|
const X: usize,
|
||||||
|
const Y: usize,
|
||||||
|
const NX: usize,
|
||||||
|
const NY: usize,
|
||||||
|
const DELAY_RESET: u32,
|
||||||
|
const PIN_MASK: u32,
|
||||||
|
const PIN_LEVEL_HIGH_ADDRESS: u32,
|
||||||
|
const PIN_LEVEL_LOW_ADDRESS: u32,
|
||||||
|
const DELAY_ZERO_HIGH: u32,
|
||||||
|
const DELAY_ZERO_LOW: u32,
|
||||||
|
const DELAY_ONE_HIGH: u32,
|
||||||
|
const DELAY_ONE_LOW: u32,
|
||||||
|
> {
|
||||||
|
buffer: [[[[u32; X]; Y]; NX]; NY],
|
||||||
|
sender: NeoPixelSender<
|
||||||
|
PIN_MASK,
|
||||||
|
PIN_LEVEL_HIGH_ADDRESS,
|
||||||
|
PIN_LEVEL_LOW_ADDRESS,
|
||||||
|
DELAY_ZERO_HIGH,
|
||||||
|
DELAY_ZERO_LOW,
|
||||||
|
DELAY_ONE_HIGH,
|
||||||
|
DELAY_ONE_LOW,
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
const X: usize,
|
||||||
|
const Y: usize,
|
||||||
|
const NX: usize,
|
||||||
|
const NY: usize,
|
||||||
|
const DELAY_RESET: u32,
|
||||||
|
const PIN_MASK: u32,
|
||||||
|
const PIN_LEVEL_HIGH_ADDRESS: u32,
|
||||||
|
const PIN_LEVEL_LOW_ADDRESS: u32,
|
||||||
|
const DELAY_ZERO_HIGH: u32,
|
||||||
|
const DELAY_ZERO_LOW: u32,
|
||||||
|
const DELAY_ONE_HIGH: u32,
|
||||||
|
const DELAY_ONE_LOW: u32,
|
||||||
|
>
|
||||||
|
CompositeNeoPixelDisplay<
|
||||||
|
X,
|
||||||
|
Y,
|
||||||
|
NX,
|
||||||
|
NY,
|
||||||
|
DELAY_RESET,
|
||||||
|
PIN_MASK,
|
||||||
|
PIN_LEVEL_HIGH_ADDRESS,
|
||||||
|
PIN_LEVEL_LOW_ADDRESS,
|
||||||
|
DELAY_ZERO_HIGH,
|
||||||
|
DELAY_ZERO_LOW,
|
||||||
|
DELAY_ONE_HIGH,
|
||||||
|
DELAY_ONE_LOW,
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let display = Self {
|
||||||
|
buffer: [[[[0x0; X]; Y]; NX]; NY],
|
||||||
|
sender: NeoPixelSender::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
display.show();
|
||||||
|
display
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn position(&self, x: usize, y: usize) -> Option<Position> {
|
||||||
|
if (x < { X * NX }) && (y < { Y * NY }) {
|
||||||
|
Some(Position::new(x, y))
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"Position({},{}) is outside the display range [(0,0);({},{})[.",
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
{ X * NX },
|
||||||
|
{ Y * NY },
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// funzies
|
||||||
|
pub fn vbar_shift_left(&mut self, color: u32, offset: usize, spacing: usize) {
|
||||||
|
for i in 0..(X * Y * NX * NY) {
|
||||||
|
if (i % spacing) == 0 {
|
||||||
|
self.point(self.position((i + offset) % (X * NX), i / (X * NX)), color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
const X: usize,
|
||||||
|
const Y: usize,
|
||||||
|
const NX: usize,
|
||||||
|
const NY: usize,
|
||||||
|
const DELAY_RESET: u32,
|
||||||
|
const PIN_MASK: u32,
|
||||||
|
const PIN_LEVEL_HIGH_ADDRESS: u32,
|
||||||
|
const PIN_LEVEL_LOW_ADDRESS: u32,
|
||||||
|
const DELAY_ZERO_HIGH: u32,
|
||||||
|
const DELAY_ZERO_LOW: u32,
|
||||||
|
const DELAY_ONE_HIGH: u32,
|
||||||
|
const DELAY_ONE_LOW: u32,
|
||||||
|
> PixelDisplay<Option<Position>>
|
||||||
|
for CompositeNeoPixelDisplay<
|
||||||
|
X,
|
||||||
|
Y,
|
||||||
|
NX,
|
||||||
|
NY,
|
||||||
|
DELAY_RESET,
|
||||||
|
PIN_MASK,
|
||||||
|
PIN_LEVEL_HIGH_ADDRESS,
|
||||||
|
PIN_LEVEL_LOW_ADDRESS,
|
||||||
|
DELAY_ZERO_HIGH,
|
||||||
|
DELAY_ZERO_LOW,
|
||||||
|
DELAY_ONE_HIGH,
|
||||||
|
DELAY_ONE_LOW,
|
||||||
|
>
|
||||||
|
{
|
||||||
|
fn show(&self) {
|
||||||
|
for r in 0..NY {
|
||||||
|
for c in 0..NX {
|
||||||
|
for y in 0..Y {
|
||||||
|
for x in 0..X {
|
||||||
|
self.sender.send(self.buffer[r][c][y][x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
asm::delay(DELAY_RESET);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self) {
|
||||||
|
self.fill(0x0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill(&mut self, value: u32) {
|
||||||
|
self.fill_rectangle(
|
||||||
|
self.position(0, 0),
|
||||||
|
self.position(X * NX - 1, Y * NY - 1),
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn point(&mut self, position: Option<Position>, value: u32) {
|
||||||
|
if let Ok(BufferPosition::<X, Y, NX, NY> { row, col, y, x }) = position.try_into() {
|
||||||
|
self.buffer[row][col][y][x] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_rectangle(&mut self, a: Option<Position>, b: Option<Position>, value: u32) {
|
||||||
|
if let Some(a) = a {
|
||||||
|
if let Some(b) = b {
|
||||||
|
let bottom_left;
|
||||||
|
let upper_right;
|
||||||
|
if a < b {
|
||||||
|
bottom_left = a;
|
||||||
|
upper_right = b;
|
||||||
|
} else {
|
||||||
|
bottom_left = b;
|
||||||
|
upper_right = a;
|
||||||
|
}
|
||||||
|
let Position { x: x0, y: y0 } = bottom_left;
|
||||||
|
let Position { x: x1, y: y1 } = upper_right;
|
||||||
|
|
||||||
|
for y in y0..=y1 {
|
||||||
|
for x in x0..=x1 {
|
||||||
|
self.point(self.position(x, y), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_circle(&mut self, origin: Option<Position>, radius: usize, value: u32) {
|
||||||
|
if let Some(origin) = origin {
|
||||||
|
let Position { x: cx, y: cy } = origin;
|
||||||
|
let y0 = if cy < radius { 0 } else { cy - radius };
|
||||||
|
let x0 = if cx < radius { 0 } else { cx - radius };
|
||||||
|
|
||||||
|
for y in y0..=(cy + radius) {
|
||||||
|
for x in x0..=(cx + radius) {
|
||||||
|
let dx = if x < cx { cx - x } else { x - cx };
|
||||||
|
let dy = if y < cy { cy - y } else { y - cy };
|
||||||
|
let h = dx * dx + dy * dy;
|
||||||
|
let epsilon = (radius * 2) + 1 / radius + 1; // todo simplify or tune some more
|
||||||
|
if h <= radius + epsilon {
|
||||||
|
self.point(self.position(x, y), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fn segment(&mut self, a: Option<Position>, b: Option<Position>, value: u32) {
|
||||||
|
// if let Some(a) = &a {
|
||||||
|
// if let Some(b) = &b {
|
||||||
|
// let start = if a < b { a } else { b };
|
||||||
|
// let end = if a < b { b } else { a };
|
||||||
|
// let dx = end.x - start.x;
|
||||||
|
// let dy = end.y - start.y;
|
||||||
|
// for x in start.x..=end.x {}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fn copy(&mut self, origin: Option<Position>, data: &[[u32]]) {
|
||||||
|
// if let Some(origin) = origin {
|
||||||
|
// let Position { x: x0, y: y0 } = origin;
|
||||||
|
// let mut iy = 0;
|
||||||
|
// let mut ix = 0;
|
||||||
|
// for y in data.iter() {
|
||||||
|
// ix = 0;
|
||||||
|
// for x in y.iter() {
|
||||||
|
// self.point(self.position(ix, iy), *x);
|
||||||
|
// ix += 1;
|
||||||
|
// }
|
||||||
|
// iy += 1;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn rectangle(&mut self, a: Option<Position>, b: Option<Position>, value: u32) {
|
||||||
|
if let Some(a) = a {
|
||||||
|
if let Some(b) = b {
|
||||||
|
let bottom_left;
|
||||||
|
let upper_right;
|
||||||
|
if a < b {
|
||||||
|
bottom_left = a;
|
||||||
|
upper_right = b;
|
||||||
|
} else {
|
||||||
|
bottom_left = b;
|
||||||
|
upper_right = a;
|
||||||
|
}
|
||||||
|
let Position { x: x0, y: y0 } = bottom_left;
|
||||||
|
let Position { x: x1, y: y1 } = upper_right;
|
||||||
|
|
||||||
|
for y in y0..=y1 {
|
||||||
|
if (y != y0) && (y != y1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for x in x0..=x1 {
|
||||||
|
if (x != x0) && (x != x1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self.point(self.position(x, y), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn circle(&mut self, origin: Option<Position>, radius: usize, value: u32) {
|
||||||
|
if let Some(origin) = origin {
|
||||||
|
let Position { x: cx, y: cy } = origin;
|
||||||
|
let y0 = if cy < radius { 0 } else { cy - radius };
|
||||||
|
let x0 = if cx < radius { 0 } else { cx - radius };
|
||||||
|
|
||||||
|
for y in y0..=(cy + radius) {
|
||||||
|
for x in x0..=(cx + radius) {
|
||||||
|
let dx = if x < cx { cx - x } else { x - cx };
|
||||||
|
let dy = if y < cy { cy - y } else { y - cy };
|
||||||
|
let r2 = radius * radius;
|
||||||
|
let h = dx * dx + dy * dy;
|
||||||
|
let d = if r2 < h { h - r2 } else { r2 - h };
|
||||||
|
let epsilon = (radius * 2 + 1) / radius + 1;
|
||||||
|
if d < epsilon {
|
||||||
|
// we use a larger epsilon to have a full line
|
||||||
|
self.point(self.position(x, y), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
tests/integration.rs
Normal file
16
tests/integration.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use template_microbit as _; // memory layout + panic handler
|
||||||
|
|
||||||
|
// See https://crates.io/crates/defmt-test/0.3.0 for more documentation (e.g. about the 'state'
|
||||||
|
// feature)
|
||||||
|
#[defmt_test::tests]
|
||||||
|
mod tests {
|
||||||
|
use defmt::assert;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert!(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user