Initial Setup for the microbit v1

This commit is contained in:
2024-10-11 20:01:36 +02:00
commit e70e2b2104
37 changed files with 22320 additions and 0 deletions

42
.cargo/config.toml Normal file
View 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
View File

@@ -0,0 +1,2 @@
/target
Cargo.lock

8
.idea/.gitignore generated vendored Normal file
View 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
View 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
View 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
View 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
View 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>

View 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>

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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);

20978
nrf51.svd Normal file

File diff suppressed because it is too large Load Diff

2
openocd.cfg Normal file
View File

@@ -0,0 +1,2 @@
source [find interface/cmsis-dap.cfg]
source [find target/nrf51.cfg]

40
openocd.gdb Normal file
View 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
View File

@@ -0,0 +1,3 @@
[toolchain]
channel = "1.82.0"
components = ["rustfmt", "llvm-tools-preview"]

7
src/bit_string/mod.rs Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}
}