From 7af983b2890f52b810885ed544bf0cb0b52574a6 Mon Sep 17 00:00:00 2001 From: David Westgate Date: Wed, 11 Dec 2024 05:59:20 -0800 Subject: [PATCH] project; add rust --- .gitignore | 9 +- code/project/Cargo.toml | 45 +++++++++ code/project/Embed.toml | 11 +++ code/project/SETUP.md | 8 ++ code/project/src/main.rs | 197 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 code/project/Cargo.toml create mode 100644 code/project/Embed.toml create mode 100644 code/project/SETUP.md create mode 100644 code/project/src/main.rs diff --git a/.gitignore b/.gitignore index 92d030a..60584b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,9 @@ target/ -code/project/*/ \ No newline at end of file +code/project/audio/ +code/project/log/ +code/project/machine/ +code/project/microbit/ +code/project/music/ +code/project/neopixel/ +code/project/radio/ +code/project/speech/ \ No newline at end of file diff --git a/code/project/Cargo.toml b/code/project/Cargo.toml new file mode 100644 index 0000000..6e6bef1 --- /dev/null +++ b/code/project/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "project" +version = "0.1.0" +edition = "2021" + + +[dependencies] +cortex-m-rt = "0.7.5" +embedded-hal = "1.0.0" +libm = "0.2.11" +microbit-v2 = "0.15.1" +rtt-target = "0.4" + +[dependencies.num-traits] +version = "0.2.15" +default-features = false +features = ["libm"] + +[dependencies.ordered-float] +version = "3.0.0" +default_features = false + +[dependencies.num-complex] +version = "0.4.2" +default-features = false +features = ["libm"] + +[dependencies.microfft] +version = "0.5.0" +default-features = false +features = ["size-64"] + +[dependencies.panic-rtt-target] +version = "0.1" +features = ["cortex-m"] + +# This works around old versions in the `microbit-v2` +# crate. You don't have to use this crate, just linking +# against it is sufficient. +[dependencies.cortex-m] +version = "0.7" +features = ["inline-asm", "critical-section-single-core"] + +[dependencies.critical-section-lock-mut] +git = "https://github.com/pdx-cs-rust-embedded/critical-section-lock-mut" diff --git a/code/project/Embed.toml b/code/project/Embed.toml new file mode 100644 index 0000000..a9e4403 --- /dev/null +++ b/code/project/Embed.toml @@ -0,0 +1,11 @@ +[default.general] +chip = "nrf52833_xxAA" + +[default.reset] +halt_afterwards = false + +[default.rtt] +enabled = true + +[default.gdb] +enabled = false \ No newline at end of file diff --git a/code/project/SETUP.md b/code/project/SETUP.md new file mode 100644 index 0000000..9d11b0a --- /dev/null +++ b/code/project/SETUP.md @@ -0,0 +1,8 @@ +``` +rustup target add thumbv7em-none-eabihf +rustup component add llvm-tools +cargo install cargo-binutils +cargo install probe-run +cargo install cargo-embed +``` + \ No newline at end of file diff --git a/code/project/src/main.rs b/code/project/src/main.rs new file mode 100644 index 0000000..ac50862 --- /dev/null +++ b/code/project/src/main.rs @@ -0,0 +1,197 @@ +#![no_std] +#![no_main] + +// Several lines of code are adapted or copied from Microbit-spectrum, by Bart Massey. These will be noted +// https://github.com/BartMassey/microbit-spectrum/blob/main/src/main.rs + +use core::f32::consts::PI; +use cortex_m_rt::entry; +use critical_section_lock_mut::LockMut; +use embedded_hal::digital::OutputPin; +use libm::{floorf, log2f}; +use microbit::{ + board::Board, + display::nonblocking::{BitImage, Display}, + hal::{ + gpio::{p0::P0_05, Floating, Input}, + saadc::SaadcConfig, + timer::Timer, + Saadc, + }, + pac::TIMER1, +}; +use microfft::real::rfft_64 as rfft; +use num_complex::Complex; +use num_traits::Float; +use ordered_float::NotNan; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; + +// Microbit-spectrum +const FFT_WIDTH: usize = 64; +/// ADC is signed; it is configured for a sample precision of 10 bits. Must match below. +const ADC_MAX: usize = 512; + +const NOTES: [&str; 12] = [ + "A", "B♭", "B", "C", "D♭", "D", "E♭", "E", "F", "G♭", "G", "A♭", +]; +static DISPLAY: LockMut> = LockMut::new(); +const DEFAULT_BITMAP: [[u8; 5]; 5] = [ + [9, 0, 0, 0, 9], + [0, 9, 0, 9, 0], + [0, 0, 9, 0, 0], + [0, 9, 0, 9, 0], + [9, 0, 0, 9, 9], +]; +// Bit representations of the 7 note, A to G in order +const NOTE_IMAGE_BITMAPS: [[[u8; 5]; 5]; 7] = [ + [ + [0, 9, 9, 0, 0], + [9, 0, 0, 9, 0], + [9, 9, 9, 9, 0], + [9, 0, 0, 9, 0], + [9, 0, 0, 9, 0], + ], + [ + [9, 9, 9, 0, 0], + [9, 0, 0, 9, 0], + [9, 9, 9, 0, 0], + [9, 0, 0, 9, 0], + [9, 9, 9, 0, 0], + ], + [ + [0, 9, 9, 9, 0], + [9, 0, 0, 0, 0], + [9, 0, 0, 0, 0], + [9, 0, 0, 0, 0], + [0, 9, 9, 9, 0], + ], + [ + [9, 9, 9, 0, 0], + [9, 0, 0, 9, 0], + [9, 0, 0, 9, 0], + [9, 0, 0, 9, 0], + [9, 9, 9, 0, 0], + ], + [ + [9, 9, 9, 9, 0], + [9, 0, 0, 0, 0], + [9, 9, 9, 0, 0], + [9, 0, 0, 0, 0], + [9, 9, 9, 9, 0], + ], + [ + [9, 9, 9, 9, 0], + [9, 0, 0, 0, 0], + [9, 9, 9, 0, 0], + [9, 0, 0, 0, 0], + [9, 0, 0, 0, 0], + ], + [ + [0, 9, 9, 9, 0], + [9, 0, 0, 0, 0], + [9, 0, 9, 9, 0], + [9, 0, 0, 9, 0], + [0, 9, 9, 9, 0], + ], +]; + +fn read_samples(saadc: &mut Saadc, mic_in: &mut P0_05>) -> [i16; FFT_WIDTH] { + let mut result = [0; FFT_WIDTH]; + for r in &mut result { + *r = saadc.read_channel(mic_in).unwrap(); + } + result +} + +// Microbit-spectrum - read function +fn samples_to_freq( + samples: [i16; FFT_WIDTH], + window: [f32; FFT_WIDTH], +) -> [Complex; FFT_WIDTH / 2] { + const fn nn(f: f32) -> NotNan { + unsafe { NotNan::new_unchecked(f) } + } + let mut sample_buf = [0.0f32; FFT_WIDTH]; + for (i, s) in sample_buf.iter_mut().enumerate() { + *s = samples[i].abs() as f32 / ADC_MAX as f32 + } + let dc = sample_buf.iter().copied().sum::() / sample_buf.len() as f32; + let peak = sample_buf + .iter() + .copied() + .map(nn) + .max() + .unwrap() + .into_inner(); + for (i, s) in sample_buf.iter_mut().enumerate() { + *s = window[i] * (*s - dc) / peak; + } + + // Take the FFT and process the result. + let freqs: &mut [Complex; FFT_WIDTH / 2] = rfft(&mut sample_buf); + *freqs +} + +fn get_dominant_freq(_freqs: [Complex; FFT_WIDTH / 2]) -> f32 { + // todo!(); + 0.0 +} + +fn get_note(freq: f32) -> (usize, f32) { + if freq > 0.0 { + let note_num = log2f(freq / 440.0) * 12.0; + let octave = floorf(4.0 + note_num / 12.0); + let rel_note = (note_num % 12.0) as usize; + return (rel_note, octave); + } + (0, 0.0) +} + +fn get_image(noctive: (usize, f32)) -> [[u8; 5]; 5] { + let (note, _octive) = noctive; // TODO - Add octive and flat modifier to display + if note > 0 { + NOTE_IMAGE_BITMAPS[note] + } else { + DEFAULT_BITMAP + } +} + +#[entry] +fn init() -> ! { + rtt_init_print!(); + let board: Board = Board::take().unwrap(); + let mut timer = Timer::new(board.TIMER0); + + let display: Display = Display::new(board.TIMER1, board.display_pins); + DISPLAY.init(display); + + let mut mic_in = board.microphone_pins.mic_in; + let mut mic_run = board.microphone_pins.mic_run; + + let _ = mic_run.set_high(); + + let saadc_config: SaadcConfig = SaadcConfig::default(); + let mut saadc: Saadc = Saadc::new(board.ADC, saadc_config); + + + let mut window = [0.0f32; FFT_WIDTH]; + // Use a Hann window, which is easy to compute and reasonably good. Source: microbit-spectrum + for (n, w) in window.iter_mut().enumerate() { + let s: f32 = (PI * n as f32 / (FFT_WIDTH - 1) as f32).sin(); + *w = s * s; + } + + loop { + let samples: [i16; FFT_WIDTH] = read_samples(&mut saadc, &mut mic_in); + let freqs: [Complex; FFT_WIDTH / 2] = samples_to_freq(samples, window); + let dominant_freq: f32 = get_dominant_freq(freqs); + let noctive: (usize, f32) = get_note(dominant_freq); //note + octive tuple + let bitmap: [[u8; 5]; 5] = get_image(noctive); + DISPLAY.with_lock(|display: &mut Display| { + display.show(&BitImage::new(&bitmap)); // XXX Not the most memory efficient - Improve + }); + rprintln!("{}", NOTES[noctive.0]); + timer.delay(400); + } +}