├── .gitignore ├── lab0 ├── .cargo │ └── config ├── .embed.toml ├── .vscode │ ├── .cortex-debug.peripherals.state.json │ ├── .cortex-debug.registers.state.json │ ├── STM32F407.svd │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── Cargo.toml ├── examples │ ├── cycle_count.rs │ ├── first.rs │ └── roulette.rs ├── memory.x ├── openocd.cfg └── openocd.gdb ├── lab1 ├── .cargo │ └── config ├── .embed.toml ├── .vscode │ ├── .cortex-debug.peripherals.state.json │ ├── .cortex-debug.registers.state.json │ ├── STM32F407.svd │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── Cargo.toml ├── examples │ ├── accelerometer_usage_i.rs │ └── accelerometer_usage_ii.rs ├── memory.x ├── openocd.cfg └── openocd.gdb ├── lab2-native ├── .vscode │ ├── .cortex-debug.peripherals.state.json │ ├── .cortex-debug.registers.state.json │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── Cargo.toml ├── examples │ ├── 2_10_sample_based_systems.rs │ ├── 2_11_frame_based_systems.rs │ ├── 2_11_iterator_based_systems.rs │ ├── 2_12.rs │ ├── 2_1_basic_signals.rs │ ├── 2_2.rs │ ├── 2_4_operating_on_signals.rs │ ├── 2_5.rs │ ├── 2_6.rs │ ├── 2_7_periodic_signals.rs │ ├── 2_8.rs │ └── 2_9.rs └── src │ └── lib.rs ├── lab2 ├── .cargo │ └── config ├── .embed.toml ├── .vscode │ ├── .cortex-debug.peripherals.state.json │ ├── .cortex-debug.registers.state.json │ ├── STM32F407.svd │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── Cargo.toml ├── build.rs ├── examples │ ├── 2_14_direct_fir_filtering.rs │ ├── 2_16_direct_fir_filtering.rs │ ├── 2_17_cmsis_fir_filtering.rs │ ├── 2_19_cmsis_fir_filtering.rs │ ├── 2_20_cmsis_convolution.rs │ ├── 2_22_cmsis_convolution.rs │ ├── 2_23_direct_iir_filtering.rs │ └── 2_25_direct_iir_filtering.rs ├── libarm_cortexM4lf_math.a ├── memory.x ├── openocd.cfg └── openocd.gdb ├── lab4-native ├── .vscode │ ├── .cortex-debug.peripherals.state.json │ ├── .cortex-debug.registers.state.json │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── Cargo.toml ├── examples │ ├── 4_10_stft_calculations.rs │ ├── 4_13_fif_calculations.rs │ ├── 4_14_linear_phase_calculations.rs │ ├── 4_15_linear_phase_calculations.rs │ ├── 4_1_dft_calculations.rs │ ├── 4_8_dtfse_calculations.rs │ └── 4_9.rs └── src │ └── lib.rs ├── lab4 ├── .cargo │ └── config ├── .embed.toml ├── .vscode │ ├── .cortex-debug.peripherals.state.json │ ├── .cortex-debug.registers.state.json │ ├── STM32F407.svd │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── Cargo.toml ├── examples │ ├── 4_11_stft_accelerometer.rs │ ├── 4_11_stft_accelerometer_microfft.rs │ ├── 4_13_fif_calculations.rs │ ├── 4_13_fif_calculations_microfft.rs │ ├── 4_1_dft_calculations.rs │ ├── 4_3_dft_calculations.rs │ ├── 4_5_fft_calculations.rs │ ├── 4_5_fft_calculations_microfft.rs │ ├── 4_6_fft_accelerometer.rs │ ├── 4_6_fft_accelerometer_microfft.rs │ ├── 4_8_dtfse_calculations.rs │ └── 4_8_dtfse_calculations_microfft.rs ├── memory.x ├── openocd.cfg └── openocd.gdb ├── lab5 ├── .cargo │ └── config ├── .embed.toml ├── .vscode │ ├── .cortex-debug.peripherals.state.json │ ├── .cortex-debug.registers.state.json │ ├── STM32F407.svd │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── Cargo.toml ├── examples │ ├── 5_1_sampling_initial_setup.rs │ └── 5_3_analog_signal_generation.rs ├── memory.x ├── openocd.cfg └── openocd.gdb ├── panic_break ├── Cargo.toml └── src │ └── lib.rs └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.rs.bk 2 | .#* 3 | .gdb_history 4 | Cargo.lock 5 | target/ 6 | 7 | # editor files 8 | .vscode/* 9 | !.vscode/*.md 10 | -------------------------------------------------------------------------------- /lab0/.cargo/config: -------------------------------------------------------------------------------- 1 | # cargo run --example whatever 2 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 3 | 4 | # openocd 5 | #runner = "arm-none-eabi-gdb -x openocd.gdb" 6 | 7 | # probe-run 8 | runner = "probe-run --chip STM32F407VGTx" 9 | 10 | rustflags = [ 11 | "-C", "link-arg=-Tlink.x", 12 | ] 13 | 14 | [build] 15 | target = "thumbv7em-none-eabihf" 16 | -------------------------------------------------------------------------------- /lab0/.embed.toml: -------------------------------------------------------------------------------- 1 | [default.rtt] 2 | enabled = true 3 | 4 | [default.general] 5 | chip = "STM32F407VGTx" 6 | -------------------------------------------------------------------------------- /lab0/.vscode/.cortex-debug.peripherals.state.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /lab0/.vscode/.cortex-debug.registers.state.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /lab0/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": [ 6 | "rust-lang.rust", 7 | "marus25.cortex-debug", 8 | ], 9 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 10 | "unwantedRecommendations": [] 11 | } -------------------------------------------------------------------------------- /lab0/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "cortex-debug", 6 | "request": "launch", 7 | "servertype": "openocd", 8 | "cwd": "${workspaceRoot}", 9 | /* dont forget to select whichever example you are debugging*/ 10 | "executable": "./target/thumbv7em-none-eabihf/debug/examples/cycle_count", 11 | "preLaunchTask": "rust: cargo build examples", 12 | "name": "Debug (OpenOCD)", 13 | "device": "STM32F407VGT6", 14 | "configFiles": [ 15 | "board/stm32f4discovery.cfg" 16 | ], 17 | "runToMain": true, 18 | "gdbpath": "gdb-multiarch", 19 | "svdFile": "${workspaceRoot}/.vscode/STM32F407.svd" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /lab0/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.target": "thumbv7em-none-eabihf", 3 | "rust-analyzer.checkOnSave.allTargets": false, 4 | "rust-analyzer.checkOnSave.extraArgs": [ 5 | "--examples" 6 | ], 7 | "files.watcherExclude": { 8 | "**/.git/objects/**": true, 9 | "**/.git/subtree-cache/**": true, 10 | "**/target/**": true 11 | } 12 | } -------------------------------------------------------------------------------- /lab0/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "cargo", 6 | "command": "build", 7 | "args": [ 8 | "--examples", 9 | ], 10 | "problemMatcher": [ 11 | "$rustc" 12 | ], 13 | "group": "build", 14 | "label": "rust: cargo build examples" 15 | }, 16 | { 17 | "type": "cargo", 18 | "command": "build", 19 | "problemMatcher": [ 20 | "$rustc" 21 | ], 22 | "group": "build", 23 | "label": "rust: cargo build" 24 | }, 25 | { 26 | "type": "cargo", 27 | "command": "clean", 28 | "problemMatcher": [ 29 | "$rustc" 30 | ], 31 | "group": "build", 32 | "label": "rust: cargo clean" 33 | }, 34 | ] 35 | } -------------------------------------------------------------------------------- /lab0/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lab0" 3 | version = "0.1.0" 4 | authors = ["Jacob Rosenthal "] 5 | edition = "2018" 6 | resolver = "2" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | [dependencies] 10 | cortex-m = "0.7.2" 11 | cortex-m-rt = "0.6.12" 12 | stm32f4xx-hal = { version = "0.9.0", features = ["stm32f407", "rt"] } 13 | panic-halt = "0.2.0" 14 | panic_break = { path = "../panic_break" } 15 | panic-rtt-target = { version = "0.1.1", features = ["cortex-m"] } 16 | rtt-target = { version = "0.3.1", features = ["cortex-m"] } 17 | heapless = { version = "0.7.0" } 18 | 19 | [dependencies.embedded-hal] 20 | features = ["unproven"] 21 | version = "0.2.5" 22 | 23 | # for cargo flash 24 | [package.metadata] 25 | chip = "STM32F407VGTx" 26 | 27 | [profile.dev] 28 | codegen-units = 1 29 | debug = 1 30 | debug-assertions = true # ! 31 | incremental = false 32 | lto = "fat" 33 | opt-level = 'z' # ! 34 | overflow-checks = false 35 | 36 | [profile.release] 37 | codegen-units = 1 38 | debug = 1 39 | debug-assertions = false 40 | incremental = false 41 | lto = "fat" 42 | opt-level = 3 43 | overflow-checks = false 44 | -------------------------------------------------------------------------------- /lab0/examples/cycle_count.rs: -------------------------------------------------------------------------------- 1 | //! This project is used to measure the code execution in terms of clock cycles. 2 | //! 3 | //! Requires `cargo install probe-run` 4 | //! 5 | //! probe-run builds, uploads, and runs your code on device and in combination 6 | //! with rtt-target and panic-break prints debug and panic information to your 7 | //! console. Its used for short running sessions like seeing the results of a 8 | //! calculation or a measurement, a panic message or backtrace of an error right 9 | //! on your command line. It exits when it detects a breakpoint. 10 | //! 11 | //!`cargo run --release --example cycle_count` 12 | 13 | #![no_std] 14 | #![no_main] 15 | 16 | use panic_break as _; 17 | use stm32f4xx_hal as hal; 18 | 19 | use hal::{dwt::ClockDuration, dwt::DwtExt, prelude::*, stm32}; 20 | use rtt_target::{rprintln, rtt_init_print}; 21 | 22 | #[cortex_m_rt::entry] 23 | fn main() -> ! { 24 | // allocate the rtt machinery for printing 25 | rtt_init_print!(BlockIfFull, 128); 26 | 27 | let dp = stm32::Peripherals::take().unwrap(); 28 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 29 | 30 | // Set up the system clock. 31 | let rcc = dp.RCC.constrain(); 32 | 33 | let clocks = rcc 34 | .cfgr 35 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 36 | .sysclk(168.mhz()) 37 | .freeze(); 38 | 39 | // Create a delay abstraction based on DWT cycle counter 40 | let dwt = cp.DWT.constrain(cp.DCB, clocks); 41 | let mut delay = dwt.delay(); 42 | 43 | let time: ClockDuration = dwt.measure(|| delay.delay_ms(100_u32)); 44 | rprintln!("ticks: {:?}", time.as_ticks()); 45 | 46 | // signal to probe-run to exit 47 | loop { 48 | cortex_m::asm::bkpt() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lab0/examples/first.rs: -------------------------------------------------------------------------------- 1 | //! Demo iterators. 2 | //! 3 | //! Requires `cargo install cargo-embed` 4 | //! 5 | //! cargo-embed builds and uploads your code and maintains a two way connection 6 | //! which in combination with panic-rtt-target and rtt-target lets you send and 7 | //! receive debug data over long lasting sessions. 8 | //! 9 | //! `cargo embed --example first --release` 10 | 11 | #![no_std] 12 | #![no_main] 13 | 14 | use panic_rtt_target as _; 15 | use stm32f4xx_hal as hal; 16 | 17 | use hal::{prelude::*, stm32}; 18 | use rtt_target::{rprintln, rtt_init_print}; 19 | 20 | static A: &[i32] = &[1, 2, 3, 4, 5]; 21 | static B: &[i32] = &[1, 2, 3, 4, 5]; 22 | const LEN: usize = 5; 23 | 24 | #[cortex_m_rt::entry] 25 | fn main() -> ! { 26 | // allocate the rtt machinery for printing 27 | rtt_init_print!(BlockIfFull, 128); 28 | 29 | let dp = stm32::Peripherals::take().unwrap(); 30 | 31 | // Set up the system clock. 32 | let rcc = dp.RCC.constrain(); 33 | 34 | let _ = rcc 35 | .cfgr 36 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 37 | .sysclk(168.mhz()) 38 | .freeze(); 39 | 40 | //can't collect into an array, so use a heapless (static) vec 41 | let c: heapless::Vec = A.iter().zip(B.iter()).map(|(a, b)| a + b).collect(); 42 | 43 | rprintln!("{:?}", c); 44 | 45 | loop {} 46 | } 47 | -------------------------------------------------------------------------------- /lab0/examples/roulette.rs: -------------------------------------------------------------------------------- 1 | //! Led Blinky Roulette example using the DWT peripheral for timing. 2 | //! 3 | //! Requires `cargo install cargo-flash` 4 | //! 5 | //! cargo-flash builds and uploads your code to run standalone. Its a more 6 | //! traditional method of uploading firmware which doesn't maintain any 7 | //! communication link for debug information. 8 | //! 9 | //! `cargo flash --example roulette --release` 10 | 11 | #![no_std] 12 | #![no_main] 13 | 14 | use panic_halt as _; 15 | use stm32f4xx_hal as hal; 16 | 17 | use hal::{dwt::DwtExt, prelude::*, stm32}; 18 | 19 | #[cortex_m_rt::entry] 20 | fn main() -> ! { 21 | let dp = stm32::Peripherals::take().unwrap(); 22 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 23 | 24 | // Set up the system clock. 25 | let rcc = dp.RCC.constrain(); 26 | 27 | let clocks = rcc 28 | .cfgr 29 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 30 | .sysclk(168.mhz()) 31 | .freeze(); 32 | 33 | // Create a delay abstraction based on DWT cycle counter 34 | let dwt = cp.DWT.constrain(cp.DCB, clocks); 35 | let mut delay = dwt.delay(); 36 | 37 | let gpiod = dp.GPIOD.split(); 38 | let mut led1 = gpiod.pd12.into_push_pull_output(); 39 | let mut led2 = gpiod.pd13.into_push_pull_output(); 40 | let mut led3 = gpiod.pd14.into_push_pull_output(); 41 | let mut led4 = gpiod.pd15.into_push_pull_output(); 42 | 43 | loop { 44 | led1.set_high().unwrap(); 45 | led2.set_low().unwrap(); 46 | led3.set_low().unwrap(); 47 | led4.set_low().unwrap(); 48 | delay.delay_ms(333_u32); 49 | 50 | led1.set_low().unwrap(); 51 | led2.set_high().unwrap(); 52 | led3.set_low().unwrap(); 53 | led4.set_low().unwrap(); 54 | delay.delay_ms(333_u32); 55 | 56 | led1.set_low().unwrap(); 57 | led2.set_low().unwrap(); 58 | led3.set_high().unwrap(); 59 | led4.set_low().unwrap(); 60 | delay.delay_ms(333_u32); 61 | 62 | led1.set_low().unwrap(); 63 | led2.set_low().unwrap(); 64 | led3.set_low().unwrap(); 65 | led4.set_high().unwrap(); 66 | delay.delay_ms(333_u32); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lab0/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE K = KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 1M 5 | RAM : ORIGIN = 0x20000000, LENGTH = 128K 6 | CCRAM : ORIGIN = 0x10000000, LENGTH = 64K 7 | } 8 | 9 | /* This is where the call stack will be allocated. */ 10 | /* The stack is of the full descending type. */ 11 | /* NOTE Do NOT modify `_stack_start` unless you know what you are doing */ 12 | _stack_start = ORIGIN(RAM) + LENGTH(RAM); 13 | -------------------------------------------------------------------------------- /lab0/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/stlink-v2.cfg] 2 | transport select hla_swd 3 | 4 | # increase working area to 64KB 5 | set WORKAREASIZE 0x10000 6 | 7 | source [find target/stm32f4x.cfg] 8 | 9 | reset_config srst_only 10 | -------------------------------------------------------------------------------- /lab0/openocd.gdb: -------------------------------------------------------------------------------- 1 | target extended-remote :3333 2 | 3 | # print demangled symbols 4 | set print asm-demangle on 5 | 6 | # set backtrace limit to not have infinite backtrace loops 7 | set backtrace limit 32 8 | 9 | # detect unhandled exceptions, hard faults and panics 10 | break DefaultHandler 11 | break HardFault 12 | break rust_begin_unwind 13 | 14 | # *try* to stop at the user entry point (it might be gone due to inlining) 15 | break main 16 | 17 | # send captured ITM to the file itm.txt 18 | # (the programmer's SWO pin on the STM32F4DISCOVERY is hard-wired to PB3. Make sure not to use it for a different purpose!) 19 | # 168000000 is the core clock frequency 20 | # monitor tpiu config internal itm.txt uart off 168000000 21 | 22 | 23 | # OR: make the microcontroller SWO (PB3) pin output compatible with UART (8N1) 24 | # 8000000 is the frequency of the SWO pin 25 | # monitor tpiu config external uart off 8000000 2000000 26 | 27 | # # enable ITM port 1 28 | # monitor itm port 1 on 29 | 30 | load 31 | 32 | # start the process but immediately halt the processor 33 | stepi 34 | -------------------------------------------------------------------------------- /lab1/.cargo/config: -------------------------------------------------------------------------------- 1 | # cargo run --example whatever 2 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 3 | 4 | # openocd 5 | #runner = "arm-none-eabi-gdb -x openocd.gdb" 6 | 7 | # probe-run 8 | runner = "probe-run --chip STM32F407VGTx" 9 | 10 | rustflags = [ 11 | "-C", "link-arg=-Tlink.x", 12 | ] 13 | 14 | [build] 15 | target = "thumbv7em-none-eabihf" 16 | -------------------------------------------------------------------------------- /lab1/.embed.toml: -------------------------------------------------------------------------------- 1 | [default.rtt] 2 | enabled = true 3 | 4 | [default.general] 5 | chip = "STM32F407VGTx" 6 | -------------------------------------------------------------------------------- /lab1/.vscode/.cortex-debug.peripherals.state.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /lab1/.vscode/.cortex-debug.registers.state.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /lab1/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": [ 6 | "rust-lang.rust", 7 | "marus25.cortex-debug", 8 | ], 9 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 10 | "unwantedRecommendations": [] 11 | } -------------------------------------------------------------------------------- /lab1/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "cortex-debug", 6 | "request": "launch", 7 | "servertype": "openocd", 8 | "cwd": "${workspaceRoot}", 9 | /* dont forget to select whichever example you are debugging*/ 10 | "executable": "./target/thumbv7em-none-eabihf/debug/examples/accelerometer_usage_i.rs", 11 | "preLaunchTask": "rust: cargo build examples", 12 | "name": "Debug (OpenOCD)", 13 | "device": "STM32F407VGT6", 14 | "configFiles": [ 15 | "board/stm32f4discovery.cfg" 16 | ], 17 | "runToMain": true, 18 | "gdbpath": "gdb-multiarch", 19 | "svdFile": "${workspaceRoot}/.vscode/STM32F407.svd" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /lab1/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.target": "thumbv7em-none-eabihf", 3 | "rust-analyzer.checkOnSave.allTargets": false, 4 | "rust-analyzer.checkOnSave.extraArgs": [ 5 | "--examples" 6 | ], 7 | "files.watcherExclude": { 8 | "**/.git/objects/**": true, 9 | "**/.git/subtree-cache/**": true, 10 | "**/target/**": true 11 | } 12 | } -------------------------------------------------------------------------------- /lab1/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "cargo", 6 | "command": "build", 7 | "args": [ 8 | "--examples", 9 | ], 10 | "problemMatcher": [ 11 | "$rustc" 12 | ], 13 | "group": "build", 14 | "label": "rust: cargo build examples" 15 | }, 16 | { 17 | "type": "cargo", 18 | "command": "build", 19 | "problemMatcher": [ 20 | "$rustc" 21 | ], 22 | "group": "build", 23 | "label": "rust: cargo build" 24 | }, 25 | { 26 | "type": "cargo", 27 | "command": "clean", 28 | "problemMatcher": [ 29 | "$rustc" 30 | ], 31 | "group": "build", 32 | "label": "rust: cargo clean" 33 | }, 34 | ] 35 | } -------------------------------------------------------------------------------- /lab1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lab1" 3 | version = "0.1.0" 4 | authors = ["Jacob Rosenthal "] 5 | edition = "2018" 6 | resolver = "2" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | [dependencies] 10 | cortex-m = "0.7.2" 11 | cortex-m-rt = "0.6.12" 12 | stm32f4xx-hal = { version = "0.9.0", features = ["stm32f407", "rt"] } 13 | panic_break = { path = "../panic_break" } 14 | panic-halt = "0.2.0" 15 | rtt-target = { version = "0.3.1", features = ["cortex-m"] } 16 | lis3dsh = { git = "https://github.com/jacobrosenthal/lis3dsh-rs", branch = "bdu" } 17 | 18 | [dependencies.embedded-hal] 19 | features = ["unproven"] 20 | version = "0.2.5" 21 | 22 | # for cargo flash 23 | [package.metadata] 24 | chip = "STM32F407VGTx" 25 | 26 | [profile.dev] 27 | codegen-units = 1 28 | debug = 1 29 | debug-assertions = true # ! 30 | incremental = false 31 | lto = "fat" 32 | opt-level = 'z' # ! 33 | overflow-checks = false 34 | 35 | [profile.release] 36 | codegen-units = 1 37 | debug = 1 38 | debug-assertions = false 39 | incremental = false 40 | lto = "fat" 41 | opt-level = 3 42 | overflow-checks = false 43 | -------------------------------------------------------------------------------- /lab1/examples/accelerometer_usage_i.rs: -------------------------------------------------------------------------------- 1 | //! Basic BSP accelerometer functions and how they are used are given below. 2 | //! 3 | //! Requires `cargo install probe-run` 4 | //! `cargo run --example accelerometer_usage_i --release` 5 | 6 | #![no_std] 7 | #![no_main] 8 | 9 | use panic_break as _; 10 | use stm32f4xx_hal as hal; 11 | 12 | use hal::{prelude::*, spi, stm32}; 13 | use lis3dsh::{accelerometer::RawAccelerometer, Lis3dsh}; 14 | use rtt_target::{rprintln, rtt_init_print}; 15 | 16 | #[cortex_m_rt::entry] 17 | fn main() -> ! { 18 | rtt_init_print!(BlockIfFull, 128); 19 | 20 | let dp = stm32::Peripherals::take().unwrap(); 21 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 22 | 23 | // Set up the system clock. 24 | let rcc = dp.RCC.constrain(); 25 | 26 | let clocks = rcc 27 | .cfgr 28 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 29 | .sysclk(168.mhz()) 30 | .freeze(); 31 | 32 | let mut delay = hal::delay::Delay::new(cp.SYST, clocks); 33 | 34 | let gpioa = dp.GPIOA.split(); 35 | let gpioe = dp.GPIOE.split(); 36 | let gpiod = dp.GPIOD.split(); 37 | 38 | let sck = gpioa.pa5.into_alternate_af5().internal_pull_up(false); 39 | let miso = gpioa.pa6.into_alternate_af5().internal_pull_up(false); 40 | let mosi = gpioa.pa7.into_alternate_af5().internal_pull_up(false); 41 | 42 | let spi = spi::Spi::spi1( 43 | dp.SPI1, 44 | (sck, miso, mosi), 45 | spi::Mode { 46 | polarity: spi::Polarity::IdleLow, 47 | phase: spi::Phase::CaptureOnFirstTransition, 48 | }, 49 | 10.mhz().into(), 50 | clocks, 51 | ); 52 | 53 | let chip_select = gpioe.pe3.into_push_pull_output(); 54 | let mut lis3dsh = Lis3dsh::new_spi(spi, chip_select); 55 | lis3dsh.init(&mut delay).unwrap(); 56 | 57 | let mut top = gpiod.pd12.into_push_pull_output(); 58 | let mut left = gpiod.pd13.into_push_pull_output(); 59 | let mut right = gpiod.pd14.into_push_pull_output(); 60 | let mut bottom = gpiod.pd15.into_push_pull_output(); 61 | 62 | loop { 63 | while !lis3dsh.is_data_ready().unwrap() {} 64 | let dat = lis3dsh.accel_raw().unwrap(); 65 | rprintln!("{:?}", dat); 66 | 67 | //not entirely sure this represents the example exactly.. 68 | if dat.z > 900 { 69 | if dat.x < 100 && dat.x > -100 && dat.y < 100 && dat.y > -100 { 70 | top.set_high().ok(); 71 | left.set_high().ok(); 72 | right.set_high().ok(); 73 | bottom.set_high().ok(); 74 | } else { 75 | top.set_low().ok(); 76 | left.set_low().ok(); 77 | right.set_low().ok(); 78 | bottom.set_low().ok(); 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lab1/examples/accelerometer_usage_ii.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for acquiring the accelerometer data as a digital 2 | //! signal. 3 | //! 4 | //! Requires `cargo install probe-run` 5 | //! `cargo run --release --example accelerometer_usage_ii` 6 | 7 | #![no_std] 8 | #![no_main] 9 | 10 | use panic_break as _; 11 | use stm32f4xx_hal as hal; 12 | 13 | use hal::{prelude::*, spi, stm32}; 14 | use lis3dsh::{accelerometer::RawAccelerometer, Lis3dsh}; 15 | use rtt_target::{rprintln, rtt_init_print}; 16 | 17 | const N: usize = 1000; 18 | 19 | #[cortex_m_rt::entry] 20 | fn main() -> ! { 21 | rtt_init_print!(BlockIfFull, 128); 22 | 23 | let dp = stm32::Peripherals::take().unwrap(); 24 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 25 | 26 | // Set up the system clock. 27 | let rcc = dp.RCC.constrain(); 28 | 29 | let clocks = rcc 30 | .cfgr 31 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 32 | .sysclk(168.mhz()) 33 | .freeze(); 34 | 35 | let mut delay = hal::delay::Delay::new(cp.SYST, clocks); 36 | 37 | let gpioa = dp.GPIOA.split(); 38 | let gpioe = dp.GPIOE.split(); 39 | 40 | let sck = gpioa.pa5.into_alternate_af5().internal_pull_up(false); 41 | let miso = gpioa.pa6.into_alternate_af5().internal_pull_up(false); 42 | let mosi = gpioa.pa7.into_alternate_af5().internal_pull_up(false); 43 | 44 | let spi = spi::Spi::spi1( 45 | dp.SPI1, 46 | (sck, miso, mosi), 47 | spi::Mode { 48 | polarity: spi::Polarity::IdleLow, 49 | phase: spi::Phase::CaptureOnFirstTransition, 50 | }, 51 | 10.mhz().into(), 52 | clocks, 53 | ); 54 | 55 | let chip_select = gpioe.pe3.into_push_pull_output(); 56 | let mut lis3dsh = Lis3dsh::new_spi(spi, chip_select); 57 | lis3dsh.init(&mut delay).unwrap(); 58 | 59 | let buffer = (0..N).map(|_i| { 60 | while !lis3dsh.is_data_ready().unwrap() {} 61 | lis3dsh.accel_raw().unwrap().x 62 | }); 63 | 64 | rprintln!("{:?}", buffer); 65 | 66 | // signal to probe-run to exit 67 | loop { 68 | cortex_m::asm::bkpt() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lab1/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE K = KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 1M 5 | RAM : ORIGIN = 0x20000000, LENGTH = 128K 6 | CCRAM : ORIGIN = 0x10000000, LENGTH = 64K 7 | } 8 | 9 | /* This is where the call stack will be allocated. */ 10 | /* The stack is of the full descending type. */ 11 | /* NOTE Do NOT modify `_stack_start` unless you know what you are doing */ 12 | _stack_start = ORIGIN(RAM) + LENGTH(RAM); -------------------------------------------------------------------------------- /lab1/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/stlink-v2.cfg] 2 | transport select hla_swd 3 | 4 | # increase working area to 64KB 5 | set WORKAREASIZE 0x10000 6 | 7 | source [find target/stm32f4x.cfg] 8 | 9 | reset_config srst_only 10 | -------------------------------------------------------------------------------- /lab1/openocd.gdb: -------------------------------------------------------------------------------- 1 | target extended-remote :3333 2 | 3 | # print demangled symbols 4 | set print asm-demangle on 5 | 6 | # set backtrace limit to not have infinite backtrace loops 7 | set backtrace limit 32 8 | 9 | # detect unhandled exceptions, hard faults and panics 10 | break DefaultHandler 11 | break HardFault 12 | break rust_begin_unwind 13 | 14 | # *try* to stop at the user entry point (it might be gone due to inlining) 15 | break main 16 | 17 | # send captured ITM to the file itm.txt 18 | # (the programmer's SWO pin on the STM32F4DISCOVERY is hard-wired to PB3. Make sure not to use it for a different purpose!) 19 | # 168000000 is the core clock frequency 20 | # monitor tpiu config internal itm.txt uart off 168000000 21 | 22 | 23 | # OR: make the microcontroller SWO (PB3) pin output compatible with UART (8N1) 24 | # 8000000 is the frequency of the SWO pin 25 | # monitor tpiu config external uart off 8000000 2000000 26 | 27 | # # enable ITM port 1 28 | # monitor itm port 1 on 29 | 30 | load 31 | 32 | # start the process but immediately halt the processor 33 | stepi 34 | -------------------------------------------------------------------------------- /lab2-native/.vscode/.cortex-debug.peripherals.state.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /lab2-native/.vscode/.cortex-debug.registers.state.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /lab2-native/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": [ 6 | "rust-lang.rust", 7 | "marus25.cortex-debug", 8 | ], 9 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 10 | "unwantedRecommendations": [] 11 | } -------------------------------------------------------------------------------- /lab2-native/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "lldb", 6 | "request": "launch", 7 | "name": "Launch", 8 | "args": [], 9 | "program": "${workspaceRoot}/target/debug/examples/2_1_basic_signals", 10 | "cwd": "${workspaceFolder}", 11 | "stopOnEntry": false, 12 | "sourceLanguages": [ 13 | "rust" 14 | ], 15 | "preLaunchTask": "cargo build --examples", 16 | }, 17 | ] 18 | } -------------------------------------------------------------------------------- /lab2-native/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.watcherExclude": { 3 | "**/.git/objects/**": true, 4 | "**/.git/subtree-cache/**": true, 5 | "**/target/**": true 6 | } 7 | } -------------------------------------------------------------------------------- /lab2-native/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | /* 8 | * This is the default cargo build task, 9 | * but we need to provide a label for it, 10 | * so we can invoke it from the debug launcher. 11 | */ 12 | "label": "cargo build", 13 | "type": "process", 14 | "command": "cargo", 15 | "args": [ 16 | "build" 17 | ], 18 | "problemMatcher": [ 19 | "$rustc" 20 | ], 21 | "group": { 22 | "kind": "build", 23 | "isDefault": true 24 | } 25 | }, 26 | { 27 | "label": "cargo build --release", 28 | "type": "process", 29 | "command": "cargo", 30 | "args": [ 31 | "build", 32 | "--release" 33 | ], 34 | "problemMatcher": [ 35 | "$rustc" 36 | ], 37 | "group": "build" 38 | }, 39 | { 40 | "label": "cargo build --examples", 41 | "type": "process", 42 | "command": "cargo", 43 | "args": [ 44 | "build", 45 | "--examples" 46 | ], 47 | "problemMatcher": [ 48 | "$rustc" 49 | ], 50 | "group": "build" 51 | }, 52 | { 53 | "label": "cargo build --examples --release", 54 | "type": "process", 55 | "command": "cargo", 56 | "args": [ 57 | "build", 58 | "--examples", 59 | "--release" 60 | ], 61 | "problemMatcher": [ 62 | "$rustc" 63 | ], 64 | "group": "build" 65 | }, 66 | { 67 | "label": "cargo clean", 68 | "type": "process", 69 | "command": "cargo", 70 | "args": [ 71 | "clean" 72 | ], 73 | "problemMatcher": [], 74 | "group": "build" 75 | }, 76 | ] 77 | } -------------------------------------------------------------------------------- /lab2-native/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lab2" 3 | version = "0.1.0" 4 | authors = ["Jacob Rosenthal "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | [dependencies] 9 | textplots = "0.6.0" 10 | heapless = { version = "0.7.0" } 11 | itertools = { version = "0.10.0", default-features = false } 12 | -------------------------------------------------------------------------------- /lab2-native/examples/2_10_sample_based_systems.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for creating eight different sample-based digital 2 | //! systems. 3 | //! 4 | //! This sample based example is less compile time checked but allows random 5 | //! access which is impossible in iterator approaches and easier than 6 | //! implementing an iterator from scratch. Note the bounds checking is elided 7 | //! for speed in release mode so make sure while developing you're running in 8 | //! debug mode to catch and fix panics due to out of bounds access See the 9 | //! prefered iterator based approaches which will be used more commonly from 10 | //! here on out. 11 | //! 12 | //! Runs entirely locally without hardware. Rounding might be different than on 13 | //! device. Except for when printing you must be vigilent to not become reliant 14 | //! on any std tools that can't otherwise port over to no_std without alloc. 15 | //! 16 | //! `cargo run --example 2_10_sample_based_systems` 17 | 18 | use lab2::{display, Shape}; 19 | 20 | const N: usize = 10; 21 | const W0: f32 = core::f32::consts::PI / 5.0; 22 | 23 | fn digital_system1(b: f32, input: f32) -> f32 { 24 | b * input 25 | } 26 | 27 | fn digital_system2(input1: f32, input2: f32) -> f32 { 28 | input1 + input2 29 | } 30 | 31 | fn digital_system3(input: f32) -> f32 { 32 | input * input 33 | } 34 | 35 | fn digital_system4(b: &[f32], input0: f32, input1: f32) -> f32 { 36 | b[0] * input0 + b[1] * input1 37 | } 38 | 39 | fn digital_system5(b: &[f32], a: f32, input0: f32, input1: f32, output: f32) -> f32 { 40 | b[0] * input0 + b[1] * input1 + a * output 41 | } 42 | 43 | fn digital_system6(b: &[f32], input0: f32, input1: f32) -> f32 { 44 | b[0] * input0 + b[1] * input1 45 | } 46 | 47 | fn digital_system7(b: f32, a: f32, input: f32, output: f32) -> f32 { 48 | b * input + a * output 49 | } 50 | 51 | fn digital_system8(n: f32, input: f32) -> f32 { 52 | n * input 53 | } 54 | 55 | fn main() { 56 | // d[n] 57 | let mut unit_pulse = [0f32; N]; 58 | unit_pulse.iter_mut().enumerate().for_each(|(n, val)| { 59 | if n == 0 { 60 | *val = 1.0; 61 | } else { 62 | *val = 0.0; 63 | } 64 | }); 65 | 66 | // u[n] 67 | let unit_step = [1f32; N]; 68 | 69 | //sinusoidal signal 70 | let mut sinusoidal = [0f32; N]; 71 | sinusoidal 72 | .iter_mut() 73 | .enumerate() 74 | .for_each(|(n, val)| *val = (W0 * n as f32).sin()); 75 | 76 | // multiplier 77 | // y[n] = b*x[n] 78 | let mut y1 = [0f32; N]; 79 | for n in 0..N { 80 | y1[n] = digital_system1(2.2, unit_step[n]); 81 | } 82 | display("digital_system1", Shape::Line, y1); 83 | 84 | // adder accumulator 85 | // y[n] = x1[n] + x2[n] 86 | let mut y2 = [0f32; N]; 87 | for n in 0..N { 88 | y2[n] = digital_system2(unit_step[n], sinusoidal[n]); 89 | } 90 | display("digital_system2", Shape::Line, y2); 91 | 92 | // squaring device 93 | // y[n] = x^2[n] 94 | let mut y3 = [0f32; N]; 95 | for n in 0..N { 96 | y3[n] = digital_system3(sinusoidal[n]); 97 | } 98 | display("digital_system3", Shape::Line, y3); 99 | 100 | // multiplier and accumulator 101 | // y[n] = b0*x[n] + b1*x[n-1] 102 | let mut y4 = [0f32; N]; 103 | for n in 0..N { 104 | if n == 0 { 105 | y4[n] = digital_system4(&[2.2, -1.1], sinusoidal[n], 0.0); 106 | } else { 107 | y4[n] = digital_system4(&[2.2, -1.1], sinusoidal[n], sinusoidal[n - 1]); 108 | } 109 | } 110 | display("digital_system4", Shape::Line, y4); 111 | 112 | // multiplier and accumulator with feedback 113 | // y[n] = b0*x[n] + b1*x[n-1] + a*y[n-1] 114 | let mut y5 = [0f32; N]; 115 | for n in 0..N { 116 | if n == 0 { 117 | y5[n] = digital_system5(&[2.2, -1.1], 0.7, sinusoidal[n], 0.0, 0.0); 118 | } else { 119 | y5[n] = digital_system5( 120 | &[2.2, -1.1], 121 | 0.7, 122 | sinusoidal[n], 123 | sinusoidal[n - 1], 124 | y5[n - 1], 125 | ); 126 | } 127 | } 128 | display("digital_system5", Shape::Line, y5); 129 | 130 | // multiplier and accumulator with future input 131 | // y[n] = b0*x[n+1] + b1*x[n] 132 | // digital_system6 in c version has oob array access, so y6[9] 0 133 | let mut y6 = [0f32; N]; 134 | for n in 0..N { 135 | // digital_system6 in c version has oob array access, should be if (n+1 < size) 136 | if n + 1 < N { 137 | y6[n] = digital_system6(&[2.2, -1.1], unit_step[n + 1], unit_step[n]); 138 | } 139 | } 140 | display("digital_system6", Shape::Line, y6); 141 | 142 | // multiplier and accumulator with unbounded output 143 | // y[n] = b0*x[n] + b1*y[n-1] 144 | let mut y7 = [0f32; N]; 145 | for n in 0..N { 146 | if n == 0 { 147 | y7[n] = digital_system7(1.0, 2.0, unit_pulse[n], 0.0); 148 | } else { 149 | y7[n] = digital_system7(1.0, 2.0, unit_pulse[n], y7[n - 1]); 150 | } 151 | } 152 | display("digital_system7", Shape::Line, y7); 153 | 154 | // multiplier with a time based coefficient 155 | // y[n]=n*x[n] 156 | let mut y8 = [0f32; N]; 157 | for n in 0..N { 158 | y8[n] = digital_system8(n as f32, sinusoidal[n]); 159 | } 160 | display("digital_system8", Shape::Line, y8); 161 | } 162 | -------------------------------------------------------------------------------- /lab2-native/examples/2_11_frame_based_systems.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for creating eight different frame-based digital 2 | //! systems. 3 | //! 4 | //! This frame based example is less compile time checked but allows random 5 | //! access which is impossible in iterator approaches and easier than 6 | //! implementing an iterator from scratch. Note the bounds checking is elided 7 | //! for speed in release mode so make sure while developing you're running in 8 | //! debug mode to catch and fix panics due to out of bounds access See the 9 | //! prefered iterator based approaches which will be used more commonly from 10 | //! here on out. 11 | //! 12 | //! Runs entirely locally without hardware. Rounding might be different than on 13 | //! device. Except for when printing you must be vigilent to not become reliant 14 | //! on any std tools that can't otherwise port over to no_std without alloc. 15 | //! 16 | //! `cargo run --example 2_11_frame_based_systems` 17 | 18 | use lab2::{display, Shape}; 19 | 20 | const N: usize = 10; 21 | const W0: f32 = core::f32::consts::PI / 5.0; 22 | 23 | fn digital_system1(b: f32, input: &[f32], output: &mut [f32]) { 24 | for n in 0..output.len() { 25 | output[n] = b * input[n]; 26 | } 27 | } 28 | 29 | fn digital_system2(input1: &[f32], input2: &[f32], output: &mut [f32]) { 30 | for n in 0..output.len() { 31 | output[n] = input1[n] + input2[n]; 32 | } 33 | } 34 | 35 | fn digital_system3(input: &[f32], output: &mut [f32]) { 36 | for n in 0..output.len() { 37 | output[n] = input[n] * input[n]; 38 | } 39 | } 40 | 41 | fn digital_system4(b: &[f32], input: &[f32], output: &mut [f32]) { 42 | for n in 0..output.len() { 43 | if n == 0 { 44 | output[n] = b[0] * input[n]; 45 | } else { 46 | output[n] = b[0] * input[n] + b[1] * input[n - 1]; 47 | } 48 | } 49 | } 50 | 51 | fn digital_system5(b: &[f32], a: f32, input: &[f32], output: &mut [f32]) { 52 | for n in 0..output.len() { 53 | if n == 0 { 54 | output[n] = b[0] * input[n]; 55 | } else { 56 | output[n] = b[0] * input[n] + b[1] * input[n - 1] + a * output[n - 1]; 57 | } 58 | } 59 | } 60 | 61 | fn digital_system6(b: &[f32], input: &[f32], output: &mut [f32]) { 62 | for n in 0..output.len() { 63 | // digital_system6 in c version has oob array access, should be if (n+1 < size) 64 | if n + 1 < input.len() { 65 | output[n] = b[0] * input[n + 1] + b[1] * input[n]; 66 | } 67 | } 68 | } 69 | 70 | fn digital_system7(b: f32, a: f32, input: &[f32], output: &mut [f32]) { 71 | for n in 0..output.len() { 72 | if n == 0 { 73 | output[n] = b * input[n]; 74 | } else { 75 | output[n] = b * input[n] + a * output[n - 1]; 76 | } 77 | } 78 | } 79 | 80 | fn digital_system8(input: &[f32], output: &mut [f32]) { 81 | for n in 0..output.len() { 82 | output[n] = n as f32 * input[n]; 83 | } 84 | } 85 | 86 | fn main() { 87 | // d[n] 88 | let mut unit_pulse = [0f32; N]; 89 | unit_pulse.iter_mut().enumerate().for_each(|(n, val)| { 90 | if n == 0 { 91 | *val = 1.0; 92 | } else { 93 | *val = 0.0; 94 | } 95 | }); 96 | 97 | // u[n] 98 | let unit_step = [1f32; N]; 99 | 100 | // s[n] 101 | let mut sinusoidal = [0f32; N]; 102 | sinusoidal 103 | .iter_mut() 104 | .enumerate() 105 | .for_each(|(n, val)| *val = (W0 * n as f32).sin()); 106 | 107 | // multiplier 108 | // y[n] = b*x[n] 109 | let mut y1 = [0f32; N]; 110 | digital_system1(2.2, &unit_step, &mut y1); 111 | display("digital_system1", Shape::Line, y1); 112 | 113 | // adder accumulator 114 | // y[n] = x1[n] + x2[n] 115 | let mut y2 = [0f32; N]; 116 | digital_system2(&unit_step, &sinusoidal, &mut y2); 117 | display("digital_system2", Shape::Line, y2); 118 | 119 | // squaring device 120 | // y[n] = x^2[n] 121 | let mut y3 = [0f32; N]; 122 | digital_system3(&sinusoidal, &mut y3); 123 | display("digital_system3", Shape::Line, y3); 124 | 125 | // multiplier and accumulator 126 | // y[n] = b0*x[n] + b1*x[n-1] 127 | let mut y4 = [0f32; N]; 128 | digital_system4(&[2.2, -1.1], &sinusoidal, &mut y4); 129 | display("digital_system4", Shape::Line, y4); 130 | 131 | // multiplier and accumulator with feedback 132 | // y[n] = b0*x[n] + b1*x[n-1] + a*y[n-1] 133 | let mut y5 = [0f32; N]; 134 | digital_system5(&[2.2, -1.1], 0.7, &sinusoidal, &mut y5); 135 | display("digital_system5", Shape::Line, y5); 136 | 137 | // multiplier and accumulator with future input 138 | // y[n] = b0*x[n+1] + b1*x[n] 139 | // digital_system6 in c version has oob array access, so y6[9] 0 140 | let mut y6 = [0f32; N]; 141 | digital_system6(&[2.2, -1.1], &unit_step, &mut y6); 142 | display("digital_system6", Shape::Line, y6); 143 | 144 | // multiplier and accumulator with unbounded output 145 | // y[n] = b0*x[n] + b1*y[n-1] 146 | let mut y7 = [0f32; N]; 147 | digital_system7(1.0, 2.0, &unit_pulse, &mut y7); 148 | display("digital_system7", Shape::Line, y7); 149 | 150 | // multiplier with a time based coefficient 151 | // y[n]=n*x[n] 152 | let mut y8 = [0f32; N]; 153 | digital_system8(&sinusoidal, &mut y8); 154 | display("digital_system8", Shape::Line, y8); 155 | } 156 | -------------------------------------------------------------------------------- /lab2-native/examples/2_11_iterator_based_systems.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for creating eight different frame-based digital 2 | //! systems. 3 | //! 4 | //! This rust implementation isnt particularly frame based, nor is it sample 5 | //! based. Both are very unrusty as they rely on array access with either bounds 6 | //! checking performance costs or no bounds checking when in release mode and 7 | //! then common UB with invald array access. Instead this is largely an iterator 8 | //! based approach. These can be easily developed inline, except where 9 | //! impossible to implement because of needing random access, theyre implemented 10 | //! as an impl Iterator on a custom struct. 11 | //! 12 | //! Runs entirely locally without hardware. Rounding might be different than on 13 | //! device. Except for when printing you must be vigilent to not become reliant 14 | //! on any std tools that can't otherwise port over to no_std without alloc. 15 | //! 16 | //! `cargo run --example 2_11_iterator_based_systems` 17 | 18 | use itertools::Itertools; 19 | use lab2::{display, Shape}; 20 | 21 | const N: usize = 10; 22 | const W0: f32 = core::f32::consts::PI / 5.0; 23 | 24 | fn main() { 25 | // d[n] 26 | let unit_pulse = (0..N).map(|val| if val == 0 { 1.0 } else { 0.0 }); 27 | 28 | // u[n] 29 | let unit_step = (0..N).map(|_| 1.0); 30 | 31 | // s[n] 32 | let sinusoidal = (0..N).map(|val| (W0 * val as f32).sin()); 33 | 34 | // multiplier 35 | // y[n] = b*x[n] 36 | let y1 = unit_step.clone().map(|u| 2.2 * u); 37 | display("digital_system1", Shape::Line, y1); 38 | 39 | // adder accumulator 40 | // y[n] = x1[n] + x2[n] 41 | let y2 = sinusoidal 42 | .clone() 43 | .zip(unit_step.clone()) 44 | .map(|(inny1, inny2)| inny1 + inny2); 45 | display("digital_system2", Shape::Line, y2); 46 | 47 | // squaring device 48 | // y[n] = x^2[n] 49 | let y3 = sinusoidal.clone().map(|inny| inny * inny); 50 | display("digital_system3", Shape::Line, y3); 51 | 52 | // multiplier and accumulator 53 | // y[n] = b0*x[n] + b1*x[n-1] 54 | let delay_sin = Delay::new(sinusoidal.clone(), 1); 55 | let y4 = sinusoidal 56 | .clone() 57 | .map(|s| 2.2 * s) 58 | .zip(delay_sin.map(|ds| ds * -1.1)) 59 | .map(|(a, b)| a + b); 60 | display("digital_system4", Shape::Line, y4); 61 | 62 | // multiplier and accumulator with feedback 63 | // y[n] = b0*x[n] + b1*x[n-1] + a*y[n-1] 64 | let y5 = DigitalSystem5::new(sinusoidal.clone()); 65 | display("digital_system5", Shape::Line, y5); 66 | 67 | // multiplier and accumulator with future input 68 | // y[n] = b0*x[n+1] + b1*x[n] 69 | // digital_system6 in c version has oob array access, should be if (n+1 < size) so y6[9] undefined 70 | let y6 = unit_step 71 | .tuple_windows() 72 | .map(|(u0, u1)| 2.2 * u1 + -1.1 * u0); 73 | display("digital_system6", Shape::Line, y6); 74 | 75 | // multiplier and accumulator with unbounded output 76 | // y[n] = b0*x[n] + b1*y[n-1] 77 | let y7 = DigitalSystem7::new(unit_pulse); 78 | display("digital_system7", Shape::Line, y7); 79 | 80 | // multiplier with a time based coefficient 81 | // y[n]=n*x[n] 82 | let y8 = sinusoidal.enumerate().map(|(n, inny)| n as f32 * inny); 83 | display("digital_system8", Shape::Line, y8); 84 | } 85 | 86 | #[derive(Clone, Debug)] 87 | struct Delay 88 | where 89 | I: Iterator, 90 | { 91 | delay: u32, 92 | idx: u32, 93 | iter: I, 94 | } 95 | 96 | impl Delay 97 | where 98 | I: Iterator, 99 | { 100 | fn new(iter: I, delay: u32) -> Self { 101 | Self { 102 | delay, 103 | idx: 0, 104 | iter, 105 | } 106 | } 107 | } 108 | 109 | impl Iterator for Delay 110 | where 111 | I: Iterator, 112 | { 113 | type Item = f32; 114 | 115 | fn next(&mut self) -> Option { 116 | if self.idx < self.delay { 117 | self.idx += 1; 118 | Some(0.0) 119 | } else { 120 | self.iter.next() 121 | } 122 | } 123 | } 124 | 125 | /// y[n] = b0*x[n] + b1*x[n-1] + a*y[n-1] 126 | #[derive(Clone, Debug)] 127 | struct DigitalSystem5 128 | where 129 | I: Iterator, 130 | { 131 | last_in: Option, 132 | last_out: Option, 133 | iter: I, 134 | } 135 | 136 | impl DigitalSystem5 137 | where 138 | I: Iterator, 139 | { 140 | fn new(iter: I) -> Self { 141 | Self { 142 | last_in: None, 143 | last_out: None, 144 | iter, 145 | } 146 | } 147 | } 148 | 149 | impl Iterator for DigitalSystem5 150 | where 151 | I: Iterator, 152 | { 153 | type Item = f32; 154 | 155 | fn next(&mut self) -> Option { 156 | if let Some(val) = self.iter.next() { 157 | let out = if let (Some(last_in), Some(last_out)) = (self.last_in, self.last_out) { 158 | 2.2 * val + -1.1 * last_in + 0.7 * last_out 159 | } else { 160 | 2.2 * val 161 | }; 162 | 163 | self.last_in = Some(val); 164 | self.last_out = Some(out); 165 | 166 | Some(out) 167 | } else { 168 | None 169 | } 170 | } 171 | } 172 | 173 | /// y[n] = b0*x[n] + b1*y[n-1] 174 | #[derive(Clone, Debug)] 175 | struct DigitalSystem7 176 | where 177 | I: Iterator, 178 | { 179 | last_out: Option, 180 | iter: I, 181 | } 182 | 183 | impl DigitalSystem7 184 | where 185 | I: Iterator, 186 | { 187 | fn new(iter: I) -> Self { 188 | Self { 189 | last_out: None, 190 | iter, 191 | } 192 | } 193 | } 194 | impl Iterator for DigitalSystem7 195 | where 196 | I: Iterator, 197 | { 198 | type Item = f32; 199 | 200 | fn next(&mut self) -> Option { 201 | if let Some(val) = self.iter.next() { 202 | self.last_out = if let Some(last_out) = self.last_out { 203 | Some(1.0 * val + 2.0 * last_out) 204 | } else { 205 | Some(1.0 * val) 206 | }; 207 | 208 | self.last_out 209 | } else { 210 | None 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /lab2-native/examples/2_12.rs: -------------------------------------------------------------------------------- 1 | //! More examples on sample-based and frame-based implementation of digital 2 | //! systems. I implemented these as iterator based, as usual. 3 | //! 4 | //! Runs entirely locally without hardware. Rounding might be different than on 5 | //! device. Except for when printing you must be vigilent to not become reliant 6 | //! on any std tools that can't otherwise port over to no_std without alloc. 7 | //! 8 | //! `cargo run --example 2_12` 9 | 10 | use itertools::Itertools; 11 | use lab2::{display, Shape}; 12 | 13 | const N: usize = 10; 14 | const A: f32 = 0.8; 15 | 16 | fn main() { 17 | // e[n] 18 | let exponential = (0..N).map(|val| A.powf(val as f32)); 19 | 20 | // r[n] 21 | let unit_ramp = (0..N).map(|n| n as f32); 22 | 23 | // y1[n]=x1[n]+x2[n], where x1[n]=r[n] and x2[n]=e[n] 24 | let y1 = unit_ramp.clone().zip(exponential).map(|(r, e)| r + e); 25 | display("y1", Shape::Line, y1.clone()); 26 | 27 | // y2[n]=x3[n], where x3[n]=r^2[n] 28 | let y2 = unit_ramp.clone().zip(unit_ramp).map(|(r, rr)| r * rr); 29 | display("y2", Shape::Line, y2.clone()); 30 | 31 | // y3[n]=2.2y1[n]-1.1y1[n-1]+.7y3[n-1] 32 | let y3 = DigitalSystem5::new(y1); 33 | display("y3", Shape::Line, y3); 34 | 35 | // y4[n]=2.2y2[n+1]-1.1y2[n] 36 | let y4 = y2.tuple_windows().map(|(y2, y2_1)| 2.2 * y2_1 - 1.1 * y2); 37 | display("y4", Shape::Line, y4); 38 | } 39 | 40 | // y3[n]=2.2y1[n]-1.1y1[n-1]+.7y3[n-1] 41 | #[derive(Clone, Debug)] 42 | struct DigitalSystem5 43 | where 44 | I: Iterator, 45 | { 46 | last_in: Option, 47 | last_out: Option, 48 | iter: I, 49 | } 50 | 51 | impl DigitalSystem5 52 | where 53 | I: Iterator, 54 | { 55 | fn new(iter: I) -> Self { 56 | Self { 57 | last_in: None, 58 | last_out: None, 59 | iter, 60 | } 61 | } 62 | } 63 | 64 | impl Iterator for DigitalSystem5 65 | where 66 | I: Iterator, 67 | { 68 | type Item = f32; 69 | 70 | fn next(&mut self) -> Option { 71 | if let Some(val) = self.iter.next() { 72 | let out = if let (Some(last_in), Some(last_out)) = (self.last_in, self.last_out) { 73 | 2.2 * val + -1.1 * last_in + 0.7 * last_out 74 | } else { 75 | 2.2 * val 76 | }; 77 | 78 | self.last_in = Some(val); 79 | self.last_out = Some(out); 80 | 81 | Some(out) 82 | } else { 83 | None 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lab2-native/examples/2_1_basic_signals.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for creating five different basic digital signals: unit 2 | //! pulse, unit step, unit ramp, exponential and sinusoidal. 3 | //! 4 | //! Runs entirely locally without hardware. Rounding might be different than on 5 | //! device. Except for when printing you must be vigilent to not become reliant 6 | //! on any std tools that can't otherwise port over to no_std without alloc. 7 | //! 8 | //! `cargo run --example 2_1_basic_signals` 9 | 10 | use lab2::{display, Shape}; 11 | 12 | const N: usize = 10; 13 | const A: f32 = 0.8; 14 | const W0: f32 = core::f32::consts::PI / 5.0; 15 | 16 | fn main() { 17 | // d[n] 18 | let unit_pulse = (0..N).map(|n| if n == 0 { 1.0 } else { 0.0 }); 19 | display("unit_pulse", Shape::Line, unit_pulse); 20 | 21 | // u[n] 22 | let unit_step = core::iter::repeat(1.0).take(N); 23 | display("unit_step", Shape::Line, unit_step); 24 | 25 | // r[n] 26 | let unit_ramp = (0..N).map(|n| n as f32); 27 | display("unit_ramp", Shape::Line, unit_ramp); 28 | 29 | // e[n] 30 | let exponential = (0..N).map(|n| A.powf(n as f32)); 31 | display("exponential", Shape::Line, exponential); 32 | 33 | // s[n] 34 | let sinusoidal = (0..N).map(|n| (W0 * n as f32).sin()); 35 | display("sinusoidal", Shape::Line, sinusoidal); 36 | } 37 | -------------------------------------------------------------------------------- /lab2-native/examples/2_2.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for creating two different digital sinusoidal signals 2 | //! with certain frequencies. 3 | //! 4 | //! Runs entirely locally without hardware. Rounding might be different than on 5 | //! device. Except for when printing you must be vigilent to not become reliant 6 | //! on any std tools that can't otherwise port over to no_std without alloc. 7 | //! 8 | //! `cargo run --example 2_2` 9 | 10 | use core::f32::consts::{FRAC_PI_4, PI}; 11 | use lab2::{display, Shape}; 12 | 13 | const N: usize = 512; 14 | 15 | fn main() { 16 | let w0: heapless::Vec = (0..N).map(|n| (PI * n as f32 / 128.0).sin()).collect(); 17 | display("w0:", Shape::Line, w0.iter().cloned()); 18 | 19 | let w1: heapless::Vec = (0..N).map(|n| (FRAC_PI_4 * n as f32).sin()).collect(); 20 | display("w1:", Shape::Line, w1.iter().cloned()); 21 | } 22 | -------------------------------------------------------------------------------- /lab2-native/examples/2_4_operating_on_signals.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for creating eight different digital signals by 2 | //! applying different operations on basic digital signals. 3 | //! 4 | //! Runs entirely locally without hardware. Rounding might be different than on 5 | //! device. Except for when printing you must be vigilent to not become reliant 6 | //! on any std tools that can't otherwise port over to no_std without alloc. 7 | //! 8 | //! `cargo run --example 2_4_operating_on_signals` 9 | 10 | use lab2::{display, Shape}; 11 | 12 | const N: usize = 10; 13 | const A: f32 = 0.8; 14 | const W0: f32 = core::f32::consts::PI / 5f32; 15 | 16 | fn main() { 17 | // d[n] 18 | let unit_pulse = (0..N).map(|val| if val == 0 { 1.0 } else { 0.0 }); 19 | 20 | // u[n] 21 | let unit_step = core::iter::repeat(1.0).take(N); 22 | 23 | // e[n] 24 | let exponential = (0..N).map(|val| A.powf(val as f32)); 25 | 26 | // s[n] 27 | let sinusoidal = (0..N).map(|val| (W0 * val as f32).sin()); 28 | 29 | // shifted unit pulse signal u[n+3] 30 | let x1 = core::iter::repeat(0.0).take(3).chain(unit_pulse).take(N); 31 | display("x1", Shape::Points, x1); 32 | 33 | // elevated sinusoidal s[n]+1.0 34 | let x2 = sinusoidal.clone().map(|ess| ess + 1.0); 35 | display("x2", Shape::Line, x2); 36 | 37 | // negated unit step -u[n] 38 | let x3 = unit_step.clone().map(|us| -us); 39 | display("x3", Shape::Line, x3); 40 | 41 | // applying all operations on the sinusoidal signal 42 | // I disagree with the book on this, x4[0] and x4[1] would be -2 shifted 43 | let x4 = core::iter::repeat(0.0) 44 | .take(2) 45 | .chain(sinusoidal.clone()) 46 | .take(N) 47 | .map(|ess| 3.0 * ess - 2.0); 48 | display("x4", Shape::Line, x4); 49 | 50 | // subtracting two unit step signals 51 | let x5 = core::iter::repeat(0.0) 52 | .take(4) 53 | .chain(unit_step.clone()) 54 | .take(N) 55 | .zip(unit_step.clone()) 56 | .map(|(us_delay, us)| us - us_delay); 57 | display("x5", Shape::Points, x5.clone()); 58 | 59 | // multiplying the exponential signal with the unit step signal 60 | let x6 = exponential.clone().zip(unit_step).map(|(ex, us)| ex * us); 61 | display("x6", Shape::Line, x6); 62 | 63 | // multiplying the exponential signal with the sinusoidal signal 64 | let x7 = exponential.clone().zip(sinusoidal).map(|(ex, ss)| ex * ss); 65 | display("x7", Shape::Line, x7); 66 | 67 | // multiplying the exponential signal with the window signal 68 | let x8 = exponential.zip(x5).map(|(ex, x5)| ex * x5); 69 | display("x8", Shape::Points, x8); 70 | } 71 | -------------------------------------------------------------------------------- /lab2-native/examples/2_5.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for creating five different digital signals by applying 2 | //! different operations on basic digital signals. You should observe that 3 | //! digital signals can be modified using different arithmetic operations with 4 | //! this application. Moreover, new digital signals can be obtained by combining 5 | //! different digital signals. 6 | //! 7 | //! Runs entirely locally without hardware. Rounding might be different than on 8 | //! device. Except for when printing you must be vigilent to not become reliant 9 | //! on any std tools that can't otherwise port over to no_std without alloc. 10 | //! 11 | //! `cargo run --example 2_5` 12 | 13 | use lab2::{display, Shape}; 14 | 15 | const N: usize = 10; 16 | const A: f32 = 0.8; 17 | const W0: f32 = core::f32::consts::PI / 5f32; 18 | 19 | fn main() { 20 | // d[n] 21 | let unit_pulse = (0..N).map(|val| if val == 0 { 1.0 } else { 0.0 }); 22 | 23 | // u[n] 24 | let unit_step = core::iter::repeat(1.0).take(N); 25 | 26 | // e[n] 27 | let exponential = (0..N).map(|val| A.powf(val as f32)); 28 | 29 | // s[n] 30 | let sinusoidal = (0..N).map(|val| (W0 * val as f32).sin()); 31 | 32 | // r[n] 33 | let unit_ramp = (0..N).map(|n| n as f32); 34 | 35 | // x1[n] =.6r[n+4] 36 | // I dont agree?... Book seems to think r[n+4] would be a window? 37 | let x1 = core::iter::repeat(0.0) 38 | .take(4) 39 | .chain(unit_ramp) 40 | .map(|dr| dr * 0.6); 41 | display("x1", Shape::Line, x1); 42 | 43 | // x2[n] = u[n-3]-u[n-8] 44 | let d3u = Delay::new(unit_step.clone(), 3); 45 | let d8u = Delay::new(unit_step.clone(), 8); 46 | let x2 = d3u.clone().zip(d8u.clone()).map(|(d3u, d8u)| d3u - d8u); 47 | display("x2", Shape::Line, x2.clone()); 48 | 49 | // x3[n] = u[n]-u[n-3]+u[n-8] 50 | let x3 = unit_step 51 | .zip(d3u) 52 | .zip(d8u) 53 | .map(|((u, d3u), d8u)| u - d3u + d8u); 54 | display("x3", Shape::Line, x3); 55 | 56 | // x4[n] = x2[n]s[n]+d[n] 57 | let x4 = x2 58 | .zip(sinusoidal.clone().zip(unit_pulse)) 59 | .map(|(x2, (s, d))| x2 * s + d); 60 | display("x4", Shape::Line, x4); 61 | 62 | // x5[n] = -2.4e[n]s[n] 63 | let x5 = exponential.zip(sinusoidal).map(|(e, s)| -2.4 * e * s); 64 | display("x5", Shape::Line, x5); 65 | } 66 | 67 | #[derive(Clone, Debug)] 68 | struct Delay 69 | where 70 | I: Iterator, 71 | { 72 | delay: u32, 73 | n: u32, 74 | iter: I, 75 | } 76 | 77 | impl Delay 78 | where 79 | I: Iterator, 80 | { 81 | fn new(iter: I, delay: u32) -> Self { 82 | Self { delay, n: 0, iter } 83 | } 84 | } 85 | 86 | impl Iterator for Delay 87 | where 88 | I: Iterator, 89 | { 90 | type Item = f32; 91 | 92 | fn next(&mut self) -> Option { 93 | if self.n < self.delay { 94 | self.n += 1; 95 | Some(0.0) 96 | } else { 97 | self.iter.next() 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lab2-native/examples/2_6.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for creating a digital signal which is sum of two 2 | //! sinusoidal signals with different frequencies. 3 | //! 4 | //! Runs entirely locally without hardware. Rounding might be different than on 5 | //! device. Except for when printing you must be vigilent to not become reliant 6 | //! on any std tools that can't otherwise port over to no_std without alloc. 7 | //! 8 | //! `cargo run --example 2_6` 9 | 10 | use core::f32::consts::{FRAC_PI_4, PI}; 11 | use lab2::{display, Shape}; 12 | 13 | const N: usize = 512; 14 | 15 | fn main() { 16 | let w0 = (0..N).map(|n| (PI * n as f32 / 128.0).sin()); 17 | 18 | let w1 = (0..N).map(|n| (FRAC_PI_4 * n as f32).sin()); 19 | 20 | let y = w0.zip(w1).map(|(inny1, inny2)| inny1 + inny2); 21 | 22 | display("w1:", Shape::Line, y); 23 | } 24 | -------------------------------------------------------------------------------- /lab2-native/examples/2_7_periodic_signals.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for creating two different digital signals. One of 2 | //! these signals is a periodic cosine wave and other one is aperiodic cosine 3 | //! wave. 4 | //! 5 | //! Runs entirely locally without hardware. Rounding might be different than on 6 | //! device. Except for when printing you must be vigilent to not become reliant 7 | //! on any std tools that can't otherwise port over to no_std without alloc. 8 | //! 9 | //! `cargo run --example 2_7_periodic_signals` 10 | 11 | use lab2::{display, Shape}; 12 | 13 | const N: usize = 100; 14 | const W1: f32 = core::f32::consts::PI / 10.0; 15 | const W2: f32 = 3.0 / 10.0; 16 | 17 | fn main() { 18 | let sinusoidal1 = (0..N).map(|n| (W1 * (n as f32)).cos()); 19 | display("sinusoidal1", Shape::Line, sinusoidal1); 20 | 21 | let sinusoidal2 = (0..N).map(|n| (W2 * (n as f32)).cos()); 22 | display("sinusoidal2", Shape::Line, sinusoidal2); 23 | } 24 | -------------------------------------------------------------------------------- /lab2-native/examples/2_8.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for creating two different digital periodic signals, a 2 | //! square and triangle singal. The use of cycle to extend the basic signal 3 | //! should emphasize the periodicity. 4 | //! 5 | //! Runs entirely locally without hardware. Rounding might be different than on 6 | //! device. Except for when printing you must be vigilent to not become reliant 7 | //! on any std tools that can't otherwise port over to no_std without alloc. 8 | //! 9 | //! `cargo run --example 2_8` 10 | 11 | use lab2::{display, Shape}; 12 | 13 | const N: usize = 100; 14 | const SQUARE_AMPLITUDE: f32 = 2.4; 15 | const SQUARE_PERIOD: usize = 50; 16 | const TRIANGLE_AMPLITUDE: f32 = 1.5; 17 | const TRIANGLE_PERIOD: usize = 40; 18 | 19 | fn main() { 20 | // Collecting to turn the Cycle into a clean iterator for our naive display fn 21 | let square: heapless::Vec = (0..SQUARE_PERIOD) 22 | .map(|n| { 23 | if n < (SQUARE_PERIOD / 2) { 24 | SQUARE_AMPLITUDE 25 | } else { 26 | -SQUARE_AMPLITUDE 27 | } 28 | }) 29 | .cycle() 30 | .take(N) 31 | .collect(); 32 | display("square signal", Shape::Line, square.iter().cloned()); 33 | 34 | // Collecting to turn the Cycle into a clean iterator for our naive display fn 35 | let triangle: heapless::Vec = (0..TRIANGLE_PERIOD) 36 | .map(|n| { 37 | let period = TRIANGLE_PERIOD as f32; 38 | 39 | if n < (TRIANGLE_PERIOD / 2) { 40 | (2.0 * TRIANGLE_AMPLITUDE / (period / 2.0)) * n as f32 - TRIANGLE_AMPLITUDE 41 | } else { 42 | -(2.0 * TRIANGLE_AMPLITUDE / (period / 2.0)) * (n as f32 - period / 2.0) 43 | + TRIANGLE_AMPLITUDE 44 | } 45 | }) 46 | .cycle() 47 | .take(N) 48 | .collect(); 49 | 50 | display("triangle signal", Shape::Line, triangle.iter().cloned()); 51 | } 52 | -------------------------------------------------------------------------------- /lab2-native/examples/2_9.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for creating a digital sawtooth signal. 2 | //! 3 | //! Runs entirely locally without hardware. Rounding might be different than on 4 | //! device. Except for when printing you must be vigilent to not become reliant 5 | //! on any std tools that can't otherwise port over to no_std without alloc. 6 | //! 7 | //! `cargo run --example 2_9` 8 | 9 | use lab2::{display, Shape}; 10 | 11 | const N: usize = 100; 12 | const SAW_AMPLITUDE: f32 = 0.75; 13 | const SAW_PERIOD: usize = 20; 14 | 15 | fn main() { 16 | // Collecting to turn the Cycle into a clean iterator for our naive display fn 17 | let sawtooth: heapless::Vec = (0..SAW_PERIOD) 18 | .map(|n| (2.0 * SAW_AMPLITUDE / (SAW_PERIOD as f32 - 1.0)) * n as f32 - SAW_AMPLITUDE) 19 | .cycle() 20 | .take(N) 21 | .collect(); 22 | 23 | display("sawtooth signal", Shape::Line, sawtooth.iter().cloned()); 24 | } 25 | -------------------------------------------------------------------------------- /lab2-native/src/lib.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use textplots::{Chart, Plot}; 3 | 4 | #[non_exhaustive] 5 | pub enum Shape { 6 | Line, 7 | Points, 8 | } 9 | 10 | pub fn display(name: &str, shape: Shape, input: I) 11 | where 12 | I: IntoIterator, 13 | ::IntoIter: Clone, 14 | ::Item: Into + std::fmt::Debug, 15 | { 16 | let i = input.into_iter(); 17 | let display: Vec<(f32, f32)> = i 18 | .clone() 19 | .enumerate() 20 | .map(|(n, y)| (n as f32, y.into())) 21 | .collect(); 22 | println!("{:?}: {:.4?}", name, i.format(", ")); 23 | 24 | let data = match shape { 25 | Shape::Line => textplots::Shape::Lines(&display), 26 | Shape::Points => textplots::Shape::Points(&display), 27 | }; 28 | 29 | let n = display.len(); 30 | let width = 256; 31 | 32 | // Continuous requires to be in a fn pointer closure which cant capture any 33 | // external data so not useful without lots of code duplication. 34 | // Lines occasionally looks good.. but mostly bad 35 | Chart::new(width as u32, 60, 0.0, n as f32) 36 | .lineplot(&data) 37 | .display(); 38 | } 39 | -------------------------------------------------------------------------------- /lab2/.cargo/config: -------------------------------------------------------------------------------- 1 | # cargo run --example whatever 2 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 3 | 4 | # openocd 5 | #runner = "arm-none-eabi-gdb -x openocd.gdb" 6 | 7 | # probe-run 8 | runner = "probe-run --chip STM32F407VGTx" 9 | 10 | rustflags = [ 11 | "-C", "link-arg=-Tlink.x", 12 | ] 13 | 14 | [build] 15 | target = "thumbv7em-none-eabihf" 16 | -------------------------------------------------------------------------------- /lab2/.embed.toml: -------------------------------------------------------------------------------- 1 | [default.rtt] 2 | enabled = true 3 | 4 | [default.general] 5 | chip = "STM32F407VGTx" 6 | -------------------------------------------------------------------------------- /lab2/.vscode/.cortex-debug.peripherals.state.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /lab2/.vscode/.cortex-debug.registers.state.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /lab2/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": [ 6 | "rust-lang.rust", 7 | "marus25.cortex-debug", 8 | ], 9 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 10 | "unwantedRecommendations": [] 11 | } -------------------------------------------------------------------------------- /lab2/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "cortex-debug", 6 | "request": "launch", 7 | "servertype": "openocd", 8 | "cwd": "${workspaceRoot}", 9 | /* dont forget to select whichever example you are debugging*/ 10 | "executable": "./target/thumbv7em-none-eabihf/debug/examples/2_14_direct_fir_filtering.rs", 11 | "preLaunchTask": "rust: cargo build examples", 12 | "name": "Debug (OpenOCD)", 13 | "device": "STM32F407VGT6", 14 | "configFiles": [ 15 | "board/stm32f4discovery.cfg" 16 | ], 17 | "runToMain": true, 18 | "gdbpath": "gdb-multiarch", 19 | "svdFile": "${workspaceRoot}/.vscode/STM32F407.svd" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /lab2/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.target": "thumbv7em-none-eabihf", 3 | "rust-analyzer.checkOnSave.allTargets": false, 4 | "rust-analyzer.checkOnSave.extraArgs": [ 5 | "--examples" 6 | ], 7 | "files.watcherExclude": { 8 | "**/.git/objects/**": true, 9 | "**/.git/subtree-cache/**": true, 10 | "**/target/**": true 11 | } 12 | } -------------------------------------------------------------------------------- /lab2/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "cargo", 6 | "command": "build", 7 | "args": [ 8 | "--examples", 9 | ], 10 | "problemMatcher": [ 11 | "$rustc" 12 | ], 13 | "group": "build", 14 | "label": "rust: cargo build examples" 15 | }, 16 | { 17 | "type": "cargo", 18 | "command": "build", 19 | "problemMatcher": [ 20 | "$rustc" 21 | ], 22 | "group": "build", 23 | "label": "rust: cargo build" 24 | }, 25 | { 26 | "type": "cargo", 27 | "command": "clean", 28 | "problemMatcher": [ 29 | "$rustc" 30 | ], 31 | "group": "build", 32 | "label": "rust: cargo clean" 33 | }, 34 | ] 35 | } -------------------------------------------------------------------------------- /lab2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lab2" 3 | version = "0.1.0" 4 | authors = ["Jacob Rosenthal "] 5 | edition = "2018" 6 | resolver = "2" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | [dependencies] 10 | cortex-m = "0.7.2" 11 | cortex-m-rt = "0.6.12" 12 | stm32f4xx-hal = { version = "0.9.0", features = ["stm32f407", "rt"] } 13 | panic_break = { path = "../panic_break" } 14 | rtt-target = { version = "0.3.1", features = ["cortex-m"] } 15 | micromath = "1.0.1" 16 | heapless = { version = "0.7.0" } 17 | itertools = { version = "0.10.0", default-features = false } 18 | cty = "0.2.1" 19 | 20 | [dependencies.embedded-hal] 21 | features = ["unproven"] 22 | version = "0.2.5" 23 | 24 | # for cargo flash 25 | [package.metadata] 26 | chip = "STM32F407VGTx" 27 | 28 | [profile.dev] 29 | codegen-units = 1 30 | debug = 1 31 | debug-assertions = true # ! 32 | incremental = false 33 | lto = "fat" 34 | opt-level = 'z' # ! 35 | overflow-checks = false 36 | 37 | [profile.release] 38 | codegen-units = 1 39 | debug = 1 40 | debug-assertions = false 41 | incremental = false 42 | lto = "fat" 43 | opt-level = 3 44 | overflow-checks = false 45 | -------------------------------------------------------------------------------- /lab2/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rerun-if-changed=build.rs"); 3 | println!("cargo:rerun-if-changed=libarm_cortexM4lf_math.a"); 4 | 5 | // Link against prebuilt cmsis math 6 | println!( 7 | "cargo:rustc-link-search={}", 8 | std::env::var("CARGO_MANIFEST_DIR").unwrap() 9 | ); 10 | println!("cargo:rustc-link-lib=static=arm_cortexM4lf_math"); 11 | } 12 | -------------------------------------------------------------------------------- /lab2/examples/2_14_direct_fir_filtering.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining FIR filtering operation using 2 | //! convolution sum operation. 3 | //! 4 | //! Requires `cargo install probe-run` 5 | //! `cargo run --release --example 2_14_direct_fir_filtering` 6 | 7 | #![no_std] 8 | #![no_main] 9 | 10 | use panic_break as _; 11 | use stm32f4xx_hal as hal; 12 | 13 | use core::f32::consts::{FRAC_PI_4, PI}; 14 | use hal::{prelude::*, stm32}; 15 | use micromath::F32Ext; 16 | use rtt_target::{rprintln, rtt_init_print}; 17 | 18 | const N: usize = 512; 19 | 20 | #[cortex_m_rt::entry] 21 | fn main() -> ! { 22 | rtt_init_print!(BlockIfFull, 128); 23 | 24 | let dp = stm32::Peripherals::take().unwrap(); 25 | let _cp = cortex_m::peripheral::Peripherals::take().unwrap(); 26 | 27 | // Set up the system clock. 28 | let rcc = dp.RCC.constrain(); 29 | 30 | let _clocks = rcc 31 | .cfgr 32 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 33 | .sysclk(168.mhz()) 34 | .freeze(); 35 | 36 | let x = (0..N).map(|n| (PI * n as f32 / 128.0).sin() + (FRAC_PI_4 * n as f32).sin()); 37 | 38 | // Collecting to have a clean iterator for our naive display fn 39 | let y: heapless::Vec = convolution_sum(x).collect(); 40 | 41 | rprintln!("y: {:?}", y); 42 | 43 | // signal to probe-run to exit 44 | loop { 45 | cortex_m::asm::bkpt() 46 | } 47 | } 48 | 49 | pub fn convolution_sum(x: I) -> impl Iterator + Clone 50 | where 51 | I: Iterator 52 | + core::iter::ExactSizeIterator 53 | + core::iter::DoubleEndedIterator 54 | + Clone, 55 | { 56 | (0..x.len()).map(move |y_n| { 57 | x.clone() 58 | .take(y_n + 1) 59 | .rev() 60 | .zip(H.iter()) 61 | .map(|(exx, h)| h * exx) 62 | .sum() 63 | }) 64 | } 65 | 66 | // low pass filter coefficients 67 | static H: &[f32] = &[ 68 | 0.002044, 0.007806, 0.014554, 0.020018, 0.024374, 0.027780, 0.030370, 0.032264, 0.033568, 69 | 0.034372, 0.034757, 0.034791, 0.034534, 0.034040, 0.033353, 0.032511, 0.031549, 0.030496, 70 | 0.029375, 0.028207, 0.027010, 0.025800, 0.024587, 0.023383, 0.022195, 0.021031, 0.019896, 71 | 0.018795, 0.017730, 0.016703, 0.015718, 0.014774, 0.013872, 0.013013, 0.012196, 0.011420, 72 | 0.010684, 0.009989, 0.009331, 0.008711, 0.008127, 0.007577, 0.007061, 0.006575, 0.006120, 73 | 0.005693, 0.005294, 0.004920, 0.004570, 0.004244, 0.003939, 0.003655, 0.003389, 0.003142, 74 | 0.002912, 0.002698, 0.002499, 0.002313, 0.002141, 0.001981, 0.001833, 0.001695, 0.001567, 75 | 0.001448, 76 | ]; 77 | 78 | // high pass filter coefficients for 2_18 79 | // static H: &[f32] = &[ 80 | // 0.705514, -0.451674, -0.234801, -0.110490, -0.041705, -0.005635, 0.011617, 0.018401, 0.019652, 81 | // 0.018216, 0.015686, 0.012909, 0.010303, 0.008042, 0.006173, 0.004677, 0.003506, 0.002605, 82 | // 0.001922, 0.001409, 0.001028, 0.000746, 0.000540, 0.000389, 0.000279, 0.000200, 0.000143, 83 | // 0.000102, 0.000072, 0.000051, 0.000036, 0.000026, 0.000018, 0.000013, 0.000009, 0.000006, 84 | // 0.000004, 0.000003, 0.000002, 0.000002, 0.000001, 0.000001, 0.000001, 0.000000, 0.000000, 85 | // 0.000000, 0.000000, 0.000000, 86 | // ]; 87 | -------------------------------------------------------------------------------- /lab2/examples/2_16_direct_fir_filtering.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for measuring memory and execution time of FIR 2 | //! filtering operation using convolution sum operation. 3 | //! 4 | //! Requires `cargo install probe-run` 5 | //! `cargo run --release --example 2_16_direct_fir_filtering` 6 | //! 7 | //! Requires `cargo install cargo-binutils` 8 | //! Requires `rustup component add llvm-tools-preview` 9 | //! `cargo size --release --example 2_16_direct_fir_filtering` 10 | 11 | #![no_std] 12 | #![no_main] 13 | 14 | use panic_break as _; 15 | use stm32f4xx_hal as hal; 16 | 17 | use core::f32::consts::{FRAC_PI_4, PI}; 18 | use hal::{dwt::ClockDuration, dwt::DwtExt, prelude::*, stm32}; 19 | use micromath::F32Ext; 20 | use rtt_target::{rprintln, rtt_init_print}; 21 | 22 | const N: usize = 512; 23 | 24 | #[cortex_m_rt::entry] 25 | fn main() -> ! { 26 | rtt_init_print!(BlockIfFull, 128); 27 | 28 | let dp = stm32::Peripherals::take().unwrap(); 29 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 30 | 31 | // Set up the system clock. 32 | let rcc = dp.RCC.constrain(); 33 | 34 | let clocks = rcc 35 | .cfgr 36 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 37 | .sysclk(168.mhz()) 38 | .freeze(); 39 | 40 | // Create a delay abstraction based on DWT cycle counter 41 | let dwt = cp.DWT.constrain(cp.DCB, clocks); 42 | 43 | let x = (0..N).map(|n| (PI * n as f32 / 128.0).sin() + (FRAC_PI_4 * n as f32).sin()); 44 | 45 | let time: ClockDuration = dwt.measure(|| { 46 | //dificult to smuggle result out of the closure so dont bother. 47 | for _ in convolution_sum(x.clone()).collect::>() { 48 | //hopefully this isnt optimized out since were not doing anything 49 | } 50 | }); 51 | 52 | rprintln!("dft ticks: {:?}", time.as_ticks()); 53 | 54 | // signal to probe-run to exit 55 | loop { 56 | cortex_m::asm::bkpt() 57 | } 58 | } 59 | 60 | pub fn convolution_sum(x: I) -> impl Iterator + Clone 61 | where 62 | I: Iterator 63 | + core::iter::ExactSizeIterator 64 | + core::iter::DoubleEndedIterator 65 | + Clone, 66 | { 67 | (0..x.len()).map(move |y_n| { 68 | x.clone() 69 | .take(y_n + 1) 70 | .rev() 71 | .zip(H.iter()) 72 | .map(|(exx, h)| h * exx) 73 | .sum() 74 | }) 75 | } 76 | 77 | // low pass filter coefficients 78 | static H: &[f32] = &[ 79 | 0.002044, 0.007806, 0.014554, 0.020018, 0.024374, 0.027780, 0.030370, 0.032264, 0.033568, 80 | 0.034372, 0.034757, 0.034791, 0.034534, 0.034040, 0.033353, 0.032511, 0.031549, 0.030496, 81 | 0.029375, 0.028207, 0.027010, 0.025800, 0.024587, 0.023383, 0.022195, 0.021031, 0.019896, 82 | 0.018795, 0.017730, 0.016703, 0.015718, 0.014774, 0.013872, 0.013013, 0.012196, 0.011420, 83 | 0.010684, 0.009989, 0.009331, 0.008711, 0.008127, 0.007577, 0.007061, 0.006575, 0.006120, 84 | 0.005693, 0.005294, 0.004920, 0.004570, 0.004244, 0.003939, 0.003655, 0.003389, 0.003142, 85 | 0.002912, 0.002698, 0.002499, 0.002313, 0.002141, 0.001981, 0.001833, 0.001695, 0.001567, 86 | 0.001448, 87 | ]; 88 | 89 | // high pass filter coefficients for 2_18 90 | // static H: &[f32] = &[ 91 | // 0.705514, -0.451674, -0.234801, -0.110490, -0.041705, -0.005635, 0.011617, 0.018401, 0.019652, 92 | // 0.018216, 0.015686, 0.012909, 0.010303, 0.008042, 0.006173, 0.004677, 0.003506, 0.002605, 93 | // 0.001922, 0.001409, 0.001028, 0.000746, 0.000540, 0.000389, 0.000279, 0.000200, 0.000143, 94 | // 0.000102, 0.000072, 0.000051, 0.000036, 0.000026, 0.000018, 0.000013, 0.000009, 0.000006, 95 | // 0.000004, 0.000003, 0.000002, 0.000002, 0.000001, 0.000001, 0.000001, 0.000000, 0.000000, 96 | // 0.000000, 0.000000, 0.000000, 97 | // ]; 98 | -------------------------------------------------------------------------------- /lab2/examples/2_17_cmsis_fir_filtering.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining FIR filtering operation using CMSIS-DSP 2 | //! library FIR function. Here we have a digital input signal, sum of two 3 | //! sinusoidal signals with different frequencies. This signal is represented 4 | //! with x array in main.c file. Originally, one of these sinusoidal signals is 5 | //! filtered out using the coefficients given in FIR_lpf_coefficients.h file. 6 | //! The output signal is represented with y array and filter coefficients are 7 | //! stored in h array in main.c file. User can replace FIR_lpf_coefficients.h 8 | //! file with FIR_hpf_coefficients.h file to filter out other sinusoidal signal. 9 | //! 10 | //! Finally, replace (#include "FIR_lpf_coefficients.h") line with (#include 11 | //! "FIR_hpf_coefficients.h") line in main.c file and repeat same steps for 12 | //! obtaining second output array. 13 | //! 14 | //! Requires `cargo install probe-run` 15 | //! `cargo run --release --example 2_17_cmsis_fir_filtering` 16 | 17 | #![no_std] 18 | #![no_main] 19 | 20 | use panic_break as _; 21 | use stm32f4xx_hal as hal; 22 | 23 | use core::f32::consts::{FRAC_PI_4, PI}; 24 | use core::mem::MaybeUninit; 25 | use cty::{c_float, c_void, uint16_t, uint32_t}; 26 | use hal::{prelude::*, stm32}; 27 | use rtt_target::{rprintln, rtt_init_print}; 28 | 29 | const N: usize = 512; 30 | const K: usize = 64; 31 | 32 | #[cortex_m_rt::entry] 33 | fn main() -> ! { 34 | rtt_init_print!(BlockIfFull, 128); 35 | 36 | let dp = stm32::Peripherals::take().unwrap(); 37 | let _cp = cortex_m::peripheral::Peripherals::take().unwrap(); 38 | 39 | // Set up the system clock. 40 | let rcc = dp.RCC.constrain(); 41 | 42 | let _clocks = rcc 43 | .cfgr 44 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 45 | .sysclk(168.mhz()) 46 | .freeze(); 47 | 48 | let mut fir_state_f32 = [0f32; N + K - 1]; 49 | 50 | let x: heapless::Vec = unsafe { 51 | (0..N) 52 | .map(|n| arm_sin_f32(PI * n as f32 / 128.0) + arm_sin_f32(FRAC_PI_4 * n as f32)) 53 | .collect() 54 | }; 55 | 56 | let h: heapless::Vec = H.iter().cloned().rev().collect(); 57 | 58 | let s = unsafe { 59 | // skips zeroing 60 | let mut s = MaybeUninit::uninit(); 61 | 62 | arm_fir_init_f32( 63 | s.as_mut_ptr(), 64 | K as uint16_t, 65 | h.as_ptr(), 66 | fir_state_f32.as_mut_ptr(), 67 | N as uint32_t, 68 | ); 69 | 70 | s.assume_init() 71 | }; 72 | 73 | let mut y = [0f32; N]; 74 | 75 | unsafe { 76 | arm_fir_f32(&s, x.as_ptr(), y.as_mut_ptr(), N as uint32_t); 77 | } 78 | 79 | rprintln!("y: {:?}", y); 80 | 81 | // signal to probe-run to exit 82 | loop { 83 | cortex_m::asm::bkpt() 84 | } 85 | } 86 | 87 | // low pass filter coefficients 88 | static H: &[f32] = &[ 89 | 0.002044, 0.007806, 0.014554, 0.020018, 0.024374, 0.027780, 0.030370, 0.032264, 0.033568, 90 | 0.034372, 0.034757, 0.034791, 0.034534, 0.034040, 0.033353, 0.032511, 0.031549, 0.030496, 91 | 0.029375, 0.028207, 0.027010, 0.025800, 0.024587, 0.023383, 0.022195, 0.021031, 0.019896, 92 | 0.018795, 0.017730, 0.016703, 0.015718, 0.014774, 0.013872, 0.013013, 0.012196, 0.011420, 93 | 0.010684, 0.009989, 0.009331, 0.008711, 0.008127, 0.007577, 0.007061, 0.006575, 0.006120, 94 | 0.005693, 0.005294, 0.004920, 0.004570, 0.004244, 0.003939, 0.003655, 0.003389, 0.003142, 95 | 0.002912, 0.002698, 0.002499, 0.002313, 0.002141, 0.001981, 0.001833, 0.001695, 0.001567, 96 | 0.001448, 97 | ]; 98 | 99 | // high pass filter coefficients for 2_18 100 | // static H: &[f32] = &[ 101 | // 0.705514, -0.451674, -0.234801, -0.110490, -0.041705, -0.005635, 0.011617, 0.018401, 0.019652, 102 | // 0.018216, 0.015686, 0.012909, 0.010303, 0.008042, 0.006173, 0.004677, 0.003506, 0.002605, 103 | // 0.001922, 0.001409, 0.001028, 0.000746, 0.000540, 0.000389, 0.000279, 0.000200, 0.000143, 104 | // 0.000102, 0.000072, 0.000051, 0.000036, 0.000026, 0.000018, 0.000013, 0.000009, 0.000006, 105 | // 0.000004, 0.000003, 0.000002, 0.000002, 0.000001, 0.000001, 0.000001, 0.000000, 0.000000, 106 | // 0.000000, 0.000000, 0.000000, 107 | // ]; 108 | 109 | // Converting CMSIS arm_math.h to expose prebuilt CMSIS 110 | // libarm_cortexM4lf_math.lib static library linked via build.rs 111 | // https://github.com/ARM-software/CMSIS_5 Todo auto convert these with bindgen 112 | // and make a nice rusty library instead 113 | extern "C" { 114 | /** 115 | * @brief Fast approximation to the trigonometric sine function for floating-point data. 116 | * @param[in] x input value in radians. 117 | * @return sin(x). 118 | */ 119 | fn arm_sin_f32(x: c_float) -> c_float; 120 | 121 | /** 122 | * @brief Initialization function for the floating-point FIR filter. 123 | * @param[in,out] S points to an instance of the floating-point FIR filter structure. 124 | * @param[in] numTaps Number of filter coefficients in the filter. 125 | * @param[in] pCoeffs points to the filter coefficients. 126 | * @param[in] pState points to the state buffer. 127 | * @param[in] blockSize number of samples that are processed at a time. 128 | */ 129 | fn arm_fir_init_f32( 130 | S: *mut arm_fir_instance_f32, //or const? 131 | numTaps: uint16_t, 132 | pCoeffs: *const c_float, 133 | pState: *mut c_float, //or const? 134 | blockSize: uint32_t, 135 | ) -> c_void; 136 | 137 | /** 138 | * @brief Processing function for the floating-point FIR filter. 139 | * @param[in] S points to an instance of the floating-point FIR structure. 140 | * @param[in] pSrc points to the block of input data. 141 | * @param[out] pDst points to the block of output data. 142 | * @param[in] blockSize number of samples to process. 143 | */ 144 | fn arm_fir_f32( 145 | S: *const arm_fir_instance_f32, 146 | pSrc: *const c_float, 147 | pDst: *mut c_float, 148 | blockSize: uint32_t, 149 | ) -> c_void; 150 | 151 | } 152 | 153 | #[repr(C)] 154 | struct arm_fir_instance_f32 { 155 | num_taps: uint16_t, 156 | p_state: *mut c_float, //or const? 157 | p_coeffs: *const c_float, 158 | } 159 | -------------------------------------------------------------------------------- /lab2/examples/2_19_cmsis_fir_filtering.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for measuring memory and execution time of FIR 2 | //! filtering operation using CMSIS-DSP library FIR function. Here we have a 3 | //! digital input signal, sum of two sinusoidal signals with different 4 | //! frequencies. This signal is represented with x array in main.c file. 5 | //! Originally, one of these sinusoidal signals is filtered out using the 6 | //! coefficients given in FIR_lpf_coefficients.h file. The output signal is 7 | //! represented with y array and filter coefficients are stored in h array in 8 | //! main.c file. User can replace FIR_lpf_coefficients.h file with 9 | //! FIR_hpf_coefficients.h file to filter out other sinusoidal signal. 10 | //! 11 | //! Finally, replace (#include "FIR_lpf_coefficients.h") line with (#include 12 | //! "FIR_hpf_coefficients.h") line in main.c file and repeat same steps for 13 | //! obtaining second output array. 14 | //! 15 | //! Requires `cargo install probe-run` 16 | //! `cargo run --release --example 2_19_cmsis_fir_filtering` 17 | //! 18 | //! Requires `cargo install cargo-binutils` 19 | //! Requires `rustup component add llvm-tools-preview` 20 | //! `cargo size --release --example 2_19_cmsis_fir_filtering` 21 | 22 | #![no_std] 23 | #![no_main] 24 | 25 | use panic_break as _; 26 | use stm32f4xx_hal as hal; 27 | 28 | use core::f32::consts::{FRAC_PI_4, PI}; 29 | use core::mem::MaybeUninit; 30 | use cty::{c_float, c_void, uint16_t, uint32_t}; 31 | use hal::{dwt::ClockDuration, dwt::DwtExt, prelude::*, stm32}; 32 | use rtt_target::{rprintln, rtt_init_print}; 33 | 34 | const N: usize = 512; 35 | const K: usize = 64; 36 | 37 | #[cortex_m_rt::entry] 38 | fn main() -> ! { 39 | rtt_init_print!(BlockIfFull, 128); 40 | 41 | let dp = stm32::Peripherals::take().unwrap(); 42 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 43 | 44 | // Set up the system clock. 45 | let rcc = dp.RCC.constrain(); 46 | 47 | let clocks = rcc 48 | .cfgr 49 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 50 | .sysclk(168.mhz()) 51 | .freeze(); 52 | 53 | // Create a delay abstraction based on DWT cycle counter 54 | let dwt = cp.DWT.constrain(cp.DCB, clocks); 55 | 56 | let mut fir_state_f32 = [0f32; N + K - 1]; 57 | 58 | let x: heapless::Vec = unsafe { 59 | (0..N) 60 | .map(|n| arm_sin_f32(PI * n as f32 / 128.0) + arm_sin_f32(FRAC_PI_4 * n as f32)) 61 | .collect() 62 | }; 63 | 64 | let h: heapless::Vec = H.iter().cloned().rev().collect(); 65 | 66 | let s = unsafe { 67 | // skips zeroing 68 | let mut s = MaybeUninit::uninit(); 69 | 70 | arm_fir_init_f32( 71 | s.as_mut_ptr(), 72 | K as uint16_t, 73 | h.as_ptr(), 74 | fir_state_f32.as_mut_ptr(), 75 | N as uint32_t, 76 | ); 77 | 78 | s.assume_init() 79 | }; 80 | 81 | let mut y = [0f32; N]; 82 | 83 | let time: ClockDuration = dwt.measure(|| unsafe { 84 | arm_fir_f32(&s, x.as_ptr(), y.as_mut_ptr(), N as uint32_t); 85 | }); 86 | 87 | rprintln!("dft ticks: {:?}", time.as_ticks()); 88 | 89 | // signal to probe-run to exit 90 | loop { 91 | cortex_m::asm::bkpt() 92 | } 93 | } 94 | 95 | // low pass filter coefficients 96 | static H: &[f32] = &[ 97 | 0.002044, 0.007806, 0.014554, 0.020018, 0.024374, 0.027780, 0.030370, 0.032264, 0.033568, 98 | 0.034372, 0.034757, 0.034791, 0.034534, 0.034040, 0.033353, 0.032511, 0.031549, 0.030496, 99 | 0.029375, 0.028207, 0.027010, 0.025800, 0.024587, 0.023383, 0.022195, 0.021031, 0.019896, 100 | 0.018795, 0.017730, 0.016703, 0.015718, 0.014774, 0.013872, 0.013013, 0.012196, 0.011420, 101 | 0.010684, 0.009989, 0.009331, 0.008711, 0.008127, 0.007577, 0.007061, 0.006575, 0.006120, 102 | 0.005693, 0.005294, 0.004920, 0.004570, 0.004244, 0.003939, 0.003655, 0.003389, 0.003142, 103 | 0.002912, 0.002698, 0.002499, 0.002313, 0.002141, 0.001981, 0.001833, 0.001695, 0.001567, 104 | 0.001448, 105 | ]; 106 | 107 | // high pass filter coefficients for 2_18 108 | // static H: &[f32] = &[ 109 | // 0.705514, -0.451674, -0.234801, -0.110490, -0.041705, -0.005635, 0.011617, 0.018401, 0.019652, 110 | // 0.018216, 0.015686, 0.012909, 0.010303, 0.008042, 0.006173, 0.004677, 0.003506, 0.002605, 111 | // 0.001922, 0.001409, 0.001028, 0.000746, 0.000540, 0.000389, 0.000279, 0.000200, 0.000143, 112 | // 0.000102, 0.000072, 0.000051, 0.000036, 0.000026, 0.000018, 0.000013, 0.000009, 0.000006, 113 | // 0.000004, 0.000003, 0.000002, 0.000002, 0.000001, 0.000001, 0.000001, 0.000000, 0.000000, 114 | // 0.000000, 0.000000, 0.000000, 115 | // ]; 116 | 117 | // Converting CMSIS arm_math.h to expose prebuilt CMSIS 118 | // libarm_cortexM4lf_math.lib static library linked via build.rs 119 | // https://github.com/ARM-software/CMSIS_5 Todo auto convert these with bindgen 120 | // and make a nice rusty library instead 121 | extern "C" { 122 | /** 123 | * @brief Fast approximation to the trigonometric sine function for floating-point data. 124 | * @param[in] x input value in radians. 125 | * @return sin(x). 126 | */ 127 | fn arm_sin_f32(x: c_float) -> c_float; 128 | 129 | /** 130 | * @brief Initialization function for the floating-point FIR filter. 131 | * @param[in,out] S points to an instance of the floating-point FIR filter structure. 132 | * @param[in] numTaps Number of filter coefficients in the filter. 133 | * @param[in] pCoeffs points to the filter coefficients. 134 | * @param[in] pState points to the state buffer. 135 | * @param[in] blockSize number of samples that are processed at a time. 136 | */ 137 | fn arm_fir_init_f32( 138 | S: *mut arm_fir_instance_f32, //or const? 139 | numTaps: uint16_t, 140 | pCoeffs: *const c_float, 141 | pState: *mut c_float, //or const? 142 | blockSize: uint32_t, 143 | ) -> c_void; 144 | 145 | /** 146 | * @brief Processing function for the floating-point FIR filter. 147 | * @param[in] S points to an instance of the floating-point FIR structure. 148 | * @param[in] pSrc points to the block of input data. 149 | * @param[out] pDst points to the block of output data. 150 | * @param[in] blockSize number of samples to process. 151 | */ 152 | fn arm_fir_f32( 153 | S: *const arm_fir_instance_f32, 154 | pSrc: *const c_float, 155 | pDst: *mut c_float, 156 | blockSize: uint32_t, 157 | ) -> c_void; 158 | 159 | } 160 | 161 | #[repr(C)] 162 | struct arm_fir_instance_f32 { 163 | num_taps: uint16_t, 164 | p_state: *mut c_float, //or const? 165 | p_coeffs: *const c_float, 166 | } 167 | -------------------------------------------------------------------------------- /lab2/examples/2_20_cmsis_convolution.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining FIR filtering operation using CMSIS-DSP 2 | //! library convolution function. Here we have a digital input signal, sum of 3 | //! two sinusoidal signals with different frequencies. This signal is 4 | //! represented with x array in main.c file. Originally, one of these sinusoidal 5 | //! signals is filtered out using the coefficients given in 6 | //! FIR_lpf_coefficients.h file. The output signal is represented with y array 7 | //! and filter coefficients are stored in h array in main.c file. User can 8 | //! replace FIR_lpf_coefficients.h file with FIR_hpf_coefficients.h file to 9 | //! filter out other sinusoidal signal. 10 | //! 11 | //! Finally, replace (#include "FIR_lpf_coefficients.h") line with (#include 12 | //! "FIR_hpf_coefficients.h") line in main.c file and repeat same steps for 13 | //! obtaining second output array. 14 | //! 15 | //! Requires `cargo install probe-run` 16 | //! `cargo run --release --example 2_20_cmsis_convolution` 17 | 18 | #![no_std] 19 | #![no_main] 20 | 21 | use panic_break as _; 22 | use stm32f4xx_hal as hal; 23 | 24 | use core::f32::consts::{FRAC_PI_4, PI}; 25 | use cty::{c_float, c_void, uint32_t}; 26 | use hal::{prelude::*, stm32}; 27 | use rtt_target::{rprintln, rtt_init_print}; 28 | 29 | const N: usize = 512; 30 | 31 | #[cortex_m_rt::entry] 32 | fn main() -> ! { 33 | rtt_init_print!(BlockIfFull, 128); 34 | 35 | let dp = stm32::Peripherals::take().unwrap(); 36 | let _cp = cortex_m::peripheral::Peripherals::take().unwrap(); 37 | 38 | // Set up the system clock. 39 | let rcc = dp.RCC.constrain(); 40 | 41 | let _clocks = rcc 42 | .cfgr 43 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 44 | .sysclk(168.mhz()) 45 | .freeze(); 46 | 47 | let x: heapless::Vec = unsafe { 48 | (0..N) 49 | .map(|n| arm_sin_f32(PI * n as f32 / 128.0) + arm_sin_f32(FRAC_PI_4 * n as f32)) 50 | .collect() 51 | }; 52 | 53 | // todo compute this. Length srcALen+srcBLen-1 54 | let mut y = [0f32; 512 + 64 - 1]; 55 | 56 | unsafe { 57 | arm_conv_f32( 58 | x.as_ptr(), 59 | N as uint32_t, 60 | H.as_ptr(), 61 | H.len() as uint32_t, 62 | y.as_mut_ptr(), 63 | ); 64 | } 65 | 66 | rprintln!("y: {:?}", y); 67 | 68 | // signal to probe-run to exit 69 | loop { 70 | cortex_m::asm::bkpt() 71 | } 72 | } 73 | 74 | // low pass filter coefficients 75 | static H: &[f32] = &[ 76 | 0.002044, 0.007806, 0.014554, 0.020018, 0.024374, 0.027780, 0.030370, 0.032264, 0.033568, 77 | 0.034372, 0.034757, 0.034791, 0.034534, 0.034040, 0.033353, 0.032511, 0.031549, 0.030496, 78 | 0.029375, 0.028207, 0.027010, 0.025800, 0.024587, 0.023383, 0.022195, 0.021031, 0.019896, 79 | 0.018795, 0.017730, 0.016703, 0.015718, 0.014774, 0.013872, 0.013013, 0.012196, 0.011420, 80 | 0.010684, 0.009989, 0.009331, 0.008711, 0.008127, 0.007577, 0.007061, 0.006575, 0.006120, 81 | 0.005693, 0.005294, 0.004920, 0.004570, 0.004244, 0.003939, 0.003655, 0.003389, 0.003142, 82 | 0.002912, 0.002698, 0.002499, 0.002313, 0.002141, 0.001981, 0.001833, 0.001695, 0.001567, 83 | 0.001448, 84 | ]; 85 | 86 | // high pass filter coefficients for 2_18 87 | // static H: &[f32] = &[ 88 | // 0.705514, -0.451674, -0.234801, -0.110490, -0.041705, -0.005635, 0.011617, 0.018401, 0.019652, 89 | // 0.018216, 0.015686, 0.012909, 0.010303, 0.008042, 0.006173, 0.004677, 0.003506, 0.002605, 90 | // 0.001922, 0.001409, 0.001028, 0.000746, 0.000540, 0.000389, 0.000279, 0.000200, 0.000143, 91 | // 0.000102, 0.000072, 0.000051, 0.000036, 0.000026, 0.000018, 0.000013, 0.000009, 0.000006, 92 | // 0.000004, 0.000003, 0.000002, 0.000002, 0.000001, 0.000001, 0.000001, 0.000000, 0.000000, 93 | // 0.000000, 0.000000, 0.000000, 94 | // ]; 95 | 96 | // Converting CMSIS arm_math.h to expose prebuilt CMSIS 97 | // libarm_cortexM4lf_math.lib static library linked via build.rs 98 | // https://github.com/ARM-software/CMSIS_5 Todo auto convert these with bindgen 99 | // and make a nice rusty library instead 100 | extern "C" { 101 | /** 102 | * @brief Fast approximation to the trigonometric sine function for floating-point data. 103 | * @param[in] x input value in radians. 104 | * @return sin(x). 105 | */ 106 | fn arm_sin_f32(x: c_float) -> c_float; 107 | 108 | /** 109 | * @brief Convolution of floating-point sequences. 110 | * @param[in] pSrcA points to the first input sequence. 111 | * @param[in] srcALen length of the first input sequence. 112 | * @param[in] pSrcB points to the second input sequence. 113 | * @param[in] srcBLen length of the second input sequence. 114 | * @param[out] pDst points to the location where the output result is written. Length srcALen+srcBLen-1. 115 | */ 116 | fn arm_conv_f32( 117 | pSrcA: *const c_float, 118 | srcALen: uint32_t, 119 | pSrcB: *const c_float, 120 | srcBLen: uint32_t, 121 | pDst: *mut c_float, 122 | ) -> c_void; 123 | 124 | } 125 | -------------------------------------------------------------------------------- /lab2/examples/2_22_cmsis_convolution.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for measuring memory and execution time of FIR 2 | //! filtering operation using CMSIS-DSP library convolution function. Here we 3 | //! have a digital input signal, sum of two sinusoidal signals with different 4 | //! frequencies. This signal is represented with x array in main.c file. 5 | //! Originally, one of these sinusoidal signals is filtered out using the 6 | //! coefficients given in FIR_lpf_coefficients.h file. The output signal is 7 | //! represented with y array and filter coefficients are stored in h array in 8 | //! main.c file. User can replace FIR_lpf_coefficients.h file with 9 | //! FIR_hpf_coefficients.h file to filter out other sinusoidal signal. 10 | //! 11 | //! Finally, replace (#include "FIR_lpf_coefficients.h") line with (#include 12 | //! "FIR_hpf_coefficients.h") line in main.c file and repeat same steps for 13 | //! obtaining second output array. 14 | //! 15 | //! Requires `cargo install probe-run` 16 | //! `cargo run --release --example 2_22_cmsis_convolution` 17 | //! 18 | //! Requires `cargo install cargo-binutils` 19 | //! Requires `rustup component add llvm-tools-preview` 20 | //! `cargo size --release --example 2_22_cmsis_convolution` 21 | 22 | #![no_std] 23 | #![no_main] 24 | 25 | use panic_break as _; 26 | use stm32f4xx_hal as hal; 27 | 28 | use core::f32::consts::{FRAC_PI_4, PI}; 29 | use cty::{c_float, c_void, uint32_t}; 30 | use hal::{dwt::ClockDuration, dwt::DwtExt, prelude::*, stm32}; 31 | use rtt_target::{rprintln, rtt_init_print}; 32 | 33 | const N: usize = 512; 34 | 35 | #[cortex_m_rt::entry] 36 | fn main() -> ! { 37 | rtt_init_print!(BlockIfFull, 128); 38 | 39 | let dp = stm32::Peripherals::take().unwrap(); 40 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 41 | 42 | // Set up the system clock. 43 | let rcc = dp.RCC.constrain(); 44 | 45 | let clocks = rcc 46 | .cfgr 47 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 48 | .sysclk(168.mhz()) 49 | .freeze(); 50 | 51 | // Create a delay abstraction based on DWT cycle counter 52 | let dwt = cp.DWT.constrain(cp.DCB, clocks); 53 | 54 | let x: heapless::Vec = unsafe { 55 | (0..N) 56 | .map(|n| arm_sin_f32(PI * n as f32 / 128.0) + arm_sin_f32(FRAC_PI_4 * n as f32)) 57 | .collect() 58 | }; 59 | 60 | // todo compute this. Length srcALen+srcBLen-1 61 | let mut y = [0f32; 512 + 64 - 1]; 62 | 63 | let time: ClockDuration = dwt.measure(|| unsafe { 64 | arm_conv_f32( 65 | x.as_ptr(), 66 | N as uint32_t, 67 | H.as_ptr(), 68 | H.len() as uint32_t, 69 | y.as_mut_ptr(), 70 | ); 71 | }); 72 | 73 | rprintln!("dft ticks: {:?}", time.as_ticks()); 74 | 75 | // signal to probe-run to exit 76 | loop { 77 | cortex_m::asm::bkpt() 78 | } 79 | } 80 | 81 | // low pass filter coefficients 82 | static H: &[f32] = &[ 83 | 0.002044, 0.007806, 0.014554, 0.020018, 0.024374, 0.027780, 0.030370, 0.032264, 0.033568, 84 | 0.034372, 0.034757, 0.034791, 0.034534, 0.034040, 0.033353, 0.032511, 0.031549, 0.030496, 85 | 0.029375, 0.028207, 0.027010, 0.025800, 0.024587, 0.023383, 0.022195, 0.021031, 0.019896, 86 | 0.018795, 0.017730, 0.016703, 0.015718, 0.014774, 0.013872, 0.013013, 0.012196, 0.011420, 87 | 0.010684, 0.009989, 0.009331, 0.008711, 0.008127, 0.007577, 0.007061, 0.006575, 0.006120, 88 | 0.005693, 0.005294, 0.004920, 0.004570, 0.004244, 0.003939, 0.003655, 0.003389, 0.003142, 89 | 0.002912, 0.002698, 0.002499, 0.002313, 0.002141, 0.001981, 0.001833, 0.001695, 0.001567, 90 | 0.001448, 91 | ]; 92 | 93 | // high pass filter coefficients for 2_18 94 | // static H: &[f32] = &[ 95 | // 0.705514, -0.451674, -0.234801, -0.110490, -0.041705, -0.005635, 0.011617, 0.018401, 0.019652, 96 | // 0.018216, 0.015686, 0.012909, 0.010303, 0.008042, 0.006173, 0.004677, 0.003506, 0.002605, 97 | // 0.001922, 0.001409, 0.001028, 0.000746, 0.000540, 0.000389, 0.000279, 0.000200, 0.000143, 98 | // 0.000102, 0.000072, 0.000051, 0.000036, 0.000026, 0.000018, 0.000013, 0.000009, 0.000006, 99 | // 0.000004, 0.000003, 0.000002, 0.000002, 0.000001, 0.000001, 0.000001, 0.000000, 0.000000, 100 | // 0.000000, 0.000000, 0.000000, 101 | // ]; 102 | 103 | // Converting CMSIS arm_math.h to expose prebuilt CMSIS 104 | // libarm_cortexM4lf_math.lib static library linked via build.rs 105 | // https://github.com/ARM-software/CMSIS_5 Todo auto convert these with bindgen 106 | // and make a nice rusty library instead 107 | extern "C" { 108 | /** 109 | * @brief Fast approximation to the trigonometric sine function for floating-point data. 110 | * @param[in] x input value in radians. 111 | * @return sin(x). 112 | */ 113 | fn arm_sin_f32(x: c_float) -> c_float; 114 | 115 | /** 116 | * @brief Convolution of floating-point sequences. 117 | * @param[in] pSrcA points to the first input sequence. 118 | * @param[in] srcALen length of the first input sequence. 119 | * @param[in] pSrcB points to the second input sequence. 120 | * @param[in] srcBLen length of the second input sequence. 121 | * @param[out] pDst points to the location where the output result is written. Length srcALen+srcBLen-1. 122 | */ 123 | fn arm_conv_f32( 124 | pSrcA: *const c_float, 125 | srcALen: uint32_t, 126 | pSrcB: *const c_float, 127 | srcBLen: uint32_t, 128 | pDst: *mut c_float, 129 | ) -> c_void; 130 | 131 | } 132 | -------------------------------------------------------------------------------- /lab2/examples/2_23_direct_iir_filtering.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining IIR filtering operation using constant 2 | //! coefficient difference equation. 3 | //! 4 | //! Requires `cargo install probe-run` 5 | //! `cargo run --release --example 2_23_direct_iir_filtering` 6 | 7 | #![no_std] 8 | #![no_main] 9 | 10 | use panic_break as _; 11 | use stm32f4xx_hal as hal; 12 | 13 | use core::f32::consts::{FRAC_PI_4, PI}; 14 | use hal::{prelude::*, stm32}; 15 | use micromath::F32Ext; 16 | use rtt_target::{rprintln, rtt_init_print}; 17 | 18 | const N: usize = 512; 19 | 20 | // high pass filter coefficients 21 | static B: &[f32] = &[0.002044, 0.004088, 0.002044]; 22 | static A: &[f32] = &[1.0, -1.819168, 0.827343]; 23 | 24 | // low pass filter coefficients for 2_24 25 | // static B: &[f32] = &[0.705514, -1.411028, 0.705514]; 26 | // static A: &[f32] = &[1.0, -1.359795, 0.462261]; 27 | 28 | #[cortex_m_rt::entry] 29 | fn main() -> ! { 30 | rtt_init_print!(BlockIfFull, 128); 31 | 32 | let dp = stm32::Peripherals::take().unwrap(); 33 | let _cp = cortex_m::peripheral::Peripherals::take().unwrap(); 34 | 35 | // Set up the system clock. 36 | let rcc = dp.RCC.constrain(); 37 | 38 | let _clocks = rcc 39 | .cfgr 40 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 41 | .sysclk(168.mhz()) 42 | .freeze(); 43 | 44 | let x: heapless::Vec = (0..N) 45 | .map(|n| (PI * n as f32 / 128.0).sin() + (FRAC_PI_4 * n as f32).sin()) 46 | .collect(); 47 | 48 | //random access of &mut y were iterating over.. so no iterators unless 49 | let mut y = [0.0; N]; 50 | for y_n in 0..N { 51 | y[y_n] = B 52 | .iter() 53 | .enumerate() 54 | .map(|(coeff_n, coeff)| { 55 | if coeff_n < (y_n + 1) { 56 | coeff * x[y_n - coeff_n] 57 | } else { 58 | 0.0 59 | } 60 | }) 61 | .sum::() 62 | + A.iter() 63 | .enumerate() 64 | .map(|(coeff_n, coeff)| { 65 | if coeff_n < (y_n + 1) { 66 | -(coeff * y[y_n - coeff_n]) 67 | } else { 68 | 0.0 69 | } 70 | }) 71 | .sum::(); 72 | } 73 | 74 | rprintln!("y: {:?}", y); 75 | 76 | // signal to probe-run to exit 77 | loop { 78 | cortex_m::asm::bkpt() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lab2/examples/2_25_direct_iir_filtering.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for measuring memory and execution time of IIR 2 | //! filtering operation using constant coefficient difference equation. 3 | //! 4 | //! Requires `cargo install probe-run` 5 | //! `cargo run --release --example 2_25_direct_iir_filtering` 6 | //! 7 | //! Requires `cargo install cargo-binutils` 8 | //! Requires `rustup component add llvm-tools-preview` 9 | //! `cargo size --release --example 2_25_direct_iir_filtering` 10 | 11 | #![no_std] 12 | #![no_main] 13 | 14 | use panic_break as _; 15 | use stm32f4xx_hal as hal; 16 | 17 | use core::f32::consts::{FRAC_PI_4, PI}; 18 | use hal::{dwt::ClockDuration, dwt::DwtExt, prelude::*, stm32}; 19 | use micromath::F32Ext; 20 | use rtt_target::{rprintln, rtt_init_print}; 21 | 22 | const N: usize = 512; 23 | 24 | // high pass filter coefficients 25 | static B: &[f32] = &[0.002044, 0.004088, 0.002044]; 26 | static A: &[f32] = &[1.0, -1.819168, 0.827343]; 27 | 28 | // low pass filter coefficients for 2_24 29 | // static B: &[f32] = &[0.705514, -1.411028, 0.705514]; 30 | // static A: &[f32] = &[1.0, -1.359795, 0.462261]; 31 | 32 | #[cortex_m_rt::entry] 33 | fn main() -> ! { 34 | rtt_init_print!(BlockIfFull, 128); 35 | 36 | let dp = stm32::Peripherals::take().unwrap(); 37 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 38 | 39 | // Set up the system clock. 40 | let rcc = dp.RCC.constrain(); 41 | 42 | let clocks = rcc 43 | .cfgr 44 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 45 | .sysclk(168.mhz()) 46 | .freeze(); 47 | 48 | // Create a delay abstraction based on DWT cycle counter 49 | let dwt = cp.DWT.constrain(cp.DCB, clocks); 50 | 51 | let x: heapless::Vec = (0..N) 52 | .map(|n| (PI * n as f32 / 128.0).sin() + (FRAC_PI_4 * n as f32).sin()) 53 | .collect(); 54 | 55 | let time: ClockDuration = dwt.measure(|| { 56 | //random access of &mut y were iterating over.. so no iterators unless 57 | let mut y = [0.0; N]; 58 | for y_n in 0..N { 59 | y[y_n] = B 60 | .iter() 61 | .enumerate() 62 | .map(|(coeff_n, coeff)| { 63 | if coeff_n < (y_n + 1) { 64 | coeff * x[y_n - coeff_n] 65 | } else { 66 | 0.0 67 | } 68 | }) 69 | .sum::() 70 | + A.iter() 71 | .enumerate() 72 | .map(|(coeff_n, coeff)| { 73 | if coeff_n < (y_n + 1) { 74 | -(coeff * y[y_n - coeff_n]) 75 | } else { 76 | 0.0 77 | } 78 | }) 79 | .sum::(); 80 | } 81 | }); 82 | 83 | rprintln!("dft ticks: {:?}", time.as_ticks()); 84 | 85 | // signal to probe-run to exit 86 | loop { 87 | cortex_m::asm::bkpt() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lab2/libarm_cortexM4lf_math.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobrosenthal/dsp-discoveryf4-rust/ca84bfb03eb1bb62cc7f25038829e2e758c45eed/lab2/libarm_cortexM4lf_math.a -------------------------------------------------------------------------------- /lab2/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE K = KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 1M 5 | RAM : ORIGIN = 0x20000000, LENGTH = 128K 6 | CCRAM : ORIGIN = 0x10000000, LENGTH = 64K 7 | } 8 | 9 | /* This is where the call stack will be allocated. */ 10 | /* The stack is of the full descending type. */ 11 | /* NOTE Do NOT modify `_stack_start` unless you know what you are doing */ 12 | _stack_start = ORIGIN(RAM) + LENGTH(RAM); 13 | -------------------------------------------------------------------------------- /lab2/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/stlink-v2.cfg] 2 | transport select hla_swd 3 | 4 | # increase working area to 64KB 5 | set WORKAREASIZE 0x10000 6 | 7 | source [find target/stm32f4x.cfg] 8 | 9 | reset_config srst_only 10 | -------------------------------------------------------------------------------- /lab2/openocd.gdb: -------------------------------------------------------------------------------- 1 | target extended-remote :3333 2 | 3 | # print demangled symbols 4 | set print asm-demangle on 5 | 6 | # set backtrace limit to not have infinite backtrace loops 7 | set backtrace limit 32 8 | 9 | # detect unhandled exceptions, hard faults and panics 10 | break DefaultHandler 11 | break HardFault 12 | break rust_begin_unwind 13 | 14 | # *try* to stop at the user entry point (it might be gone due to inlining) 15 | break main 16 | 17 | # send captured ITM to the file itm.txt 18 | # (the programmer's SWO pin on the STM32F4DISCOVERY is hard-wired to PB3. Make sure not to use it for a different purpose!) 19 | # 168000000 is the core clock frequency 20 | # monitor tpiu config internal itm.txt uart off 168000000 21 | 22 | 23 | # OR: make the microcontroller SWO (PB3) pin output compatible with UART (8N1) 24 | # 8000000 is the frequency of the SWO pin 25 | # monitor tpiu config external uart off 8000000 2000000 26 | 27 | # # enable ITM port 1 28 | # monitor itm port 1 on 29 | 30 | load 31 | 32 | # start the process but immediately halt the processor 33 | stepi 34 | -------------------------------------------------------------------------------- /lab4-native/.vscode/.cortex-debug.peripherals.state.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /lab4-native/.vscode/.cortex-debug.registers.state.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /lab4-native/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": [ 6 | "rust-lang.rust", 7 | "marus25.cortex-debug", 8 | ], 9 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 10 | "unwantedRecommendations": [] 11 | } -------------------------------------------------------------------------------- /lab4-native/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "lldb", 6 | "request": "launch", 7 | "name": "Launch", 8 | "args": [], 9 | "program": "${workspaceRoot}/target/debug/examples/2_1_basic_signals", 10 | "cwd": "${workspaceFolder}", 11 | "stopOnEntry": false, 12 | "sourceLanguages": [ 13 | "rust" 14 | ], 15 | "preLaunchTask": "cargo build --examples", 16 | }, 17 | ] 18 | } -------------------------------------------------------------------------------- /lab4-native/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.watcherExclude": { 3 | "**/.git/objects/**": true, 4 | "**/.git/subtree-cache/**": true, 5 | "**/target/**": true 6 | } 7 | } -------------------------------------------------------------------------------- /lab4-native/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | /* 8 | * This is the default cargo build task, 9 | * but we need to provide a label for it, 10 | * so we can invoke it from the debug launcher. 11 | */ 12 | "label": "cargo build", 13 | "type": "process", 14 | "command": "cargo", 15 | "args": [ 16 | "build" 17 | ], 18 | "problemMatcher": [ 19 | "$rustc" 20 | ], 21 | "group": { 22 | "kind": "build", 23 | "isDefault": true 24 | } 25 | }, 26 | { 27 | "label": "cargo build --release", 28 | "type": "process", 29 | "command": "cargo", 30 | "args": [ 31 | "build", 32 | "--release" 33 | ], 34 | "problemMatcher": [ 35 | "$rustc" 36 | ], 37 | "group": "build" 38 | }, 39 | { 40 | "label": "cargo build --examples", 41 | "type": "process", 42 | "command": "cargo", 43 | "args": [ 44 | "build", 45 | "--examples" 46 | ], 47 | "problemMatcher": [ 48 | "$rustc" 49 | ], 50 | "group": "build" 51 | }, 52 | { 53 | "label": "cargo build --examples --release", 54 | "type": "process", 55 | "command": "cargo", 56 | "args": [ 57 | "build", 58 | "--examples", 59 | "--release" 60 | ], 61 | "problemMatcher": [ 62 | "$rustc" 63 | ], 64 | "group": "build" 65 | }, 66 | { 67 | "label": "cargo clean", 68 | "type": "process", 69 | "command": "cargo", 70 | "args": [ 71 | "clean" 72 | ], 73 | "problemMatcher": [], 74 | "group": "build" 75 | }, 76 | ] 77 | } -------------------------------------------------------------------------------- /lab4-native/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lab4" 3 | version = "0.1.0" 4 | authors = ["Jacob Rosenthal "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | [dependencies] 9 | textplots = "0.6.0" 10 | heapless = { version = "0.7.0" } 11 | itertools = { version = "0.10.0", default-features = false } 12 | microfft = "0.4.0" 13 | image = "0.23.5" 14 | smart-leds = "0.3.0" 15 | plotly = "0.6.0" 16 | -------------------------------------------------------------------------------- /lab4-native/examples/4_13_fif_calculations.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining filtering in frequency domain. Here, we 2 | //! have a digital input signal as the sum of two sinusoids with different 3 | //! frequencies. The complex form of this signal is represented with s_complex 4 | //! array in main.c file. Also we have a digital filter represented with h array 5 | //! given in FIR_lpf_coefficients.h file. 6 | //! 7 | //! Runs entirely locally without hardware. Rounding might be different than on 8 | //! device. Except for when printing you must be vigilent to not become reliant 9 | //! on any std tools that can't otherwise port over to no_std without alloc. 10 | //! 11 | //! `cargo run --example 4_10_stft_calculations` 12 | 13 | use core::f32::consts::PI; 14 | use lab4::{display, Shape}; 15 | use microfft::Complex32; 16 | 17 | use microfft::complex::cfft_512 as cfft; 18 | const N: usize = 512; 19 | 20 | const W1: f32 = core::f32::consts::PI / 128.0; 21 | const W2: f32 = core::f32::consts::PI / 4.0; 22 | 23 | fn main() { 24 | // Complex sum of sinusoidal signals 25 | let s1 = (0..N).map(|val| (W1 * val as f32).sin()); 26 | let s2 = (0..N).map(|val| (W2 * val as f32).sin()); 27 | let s = s1.zip(s2).map(|(ess1, ess2)| ess1 + ess2); 28 | 29 | let mut s_complex: heapless::Vec = 30 | s.clone().map(|f| Complex32 { re: f, im: 0.0 }).collect(); 31 | 32 | // Complex impulse response of filter 33 | let mut df_complex: heapless::Vec = H 34 | .iter() 35 | .cloned() 36 | .map(|f| Complex32 { re: f, im: 0.0 }) 37 | .chain(core::iter::repeat(Complex32 { re: 0.0, im: 0.0 })) 38 | //fill rest with zeros up to N 39 | .take(N) 40 | .collect(); 41 | 42 | // SAFETY microfft now only accepts arrays instead of slices to avoid runtime errors 43 | // Thats not great for us. However we can cheat since our slice into an array because 44 | // "The layout of a slice [T] of length N is the same as that of a [T; N] array." 45 | // https://rust-lang.github.io/unsafe-code-guidelines/layout/arrays-and-slices.html 46 | // this goes away when something like heapless vec is in standard library 47 | // https://github.com/rust-lang/rfcs/pull/2990 48 | unsafe { 49 | let ptr = &mut *(df_complex.as_mut_ptr() as *mut [Complex32; N]); 50 | 51 | // Finding the FFT of the filter 52 | let _ = cfft(ptr); 53 | } 54 | 55 | // SAFETY same as above 56 | unsafe { 57 | let ptr = &mut *(s_complex.as_mut_ptr() as *mut [Complex32; N]); 58 | 59 | // Finding the FFT of the input signal 60 | let _ = cfft(ptr); 61 | } 62 | 63 | // Filtering in the frequency domain 64 | let y_complex = s_complex 65 | .iter() 66 | .zip(df_complex.iter()) 67 | //multiply complex 68 | .map(|(s, df)| Complex32 { 69 | re: s.re * df.re - s.im * df.im, 70 | im: s.re * df.im + s.im * df.re, 71 | }); 72 | 73 | // Finding the complex result in time domain 74 | // supposed to be inverse transform but microfft doesnt have it 75 | // Could patch it in. inverse DFT is the same as the DFT, but with the 76 | // opposite sign in the exponent and a 1/N factor, any FFT algorithm can 77 | // easily be adapted for it. 78 | // just dtfse approx instead for now 79 | let y_freq: heapless::Vec = dtfse(y_complex.clone(), 15).collect(); 80 | display("freq", Shape::Line, y_freq.iter().cloned()); 81 | 82 | //y_time via convolution_sum developed in 2.14 to compare 83 | let y_time: heapless::Vec = convolution_sum(s).collect(); 84 | display("time", Shape::Line, y_time.iter().cloned()); 85 | } 86 | 87 | static H: &[f32] = &[ 88 | 0.002044, 0.007806, 0.014554, 0.020018, 0.024374, 0.027780, 0.030370, 0.032264, 0.033568, 89 | 0.034372, 0.034757, 0.034791, 0.034534, 0.034040, 0.033353, 0.032511, 0.031549, 0.030496, 90 | 0.029375, 0.028207, 0.027010, 0.025800, 0.024587, 0.023383, 0.022195, 0.021031, 0.019896, 91 | 0.018795, 0.017730, 0.016703, 0.015718, 0.014774, 0.013872, 0.013013, 0.012196, 0.011420, 92 | 0.010684, 0.009989, 0.009331, 0.008711, 0.008127, 0.007577, 0.007061, 0.006575, 0.006120, 93 | 0.005693, 0.005294, 0.004920, 0.004570, 0.004244, 0.003939, 0.003655, 0.003389, 0.003142, 94 | 0.002912, 0.002698, 0.002499, 0.002313, 0.002141, 0.001981, 0.001833, 0.001695, 0.001567, 95 | 0.001448, 96 | ]; 97 | 98 | fn dtfse + Clone>( 99 | coeff: I, 100 | k_var: usize, 101 | ) -> impl Iterator { 102 | let size = N as f32; 103 | (0..N).map(move |n| { 104 | coeff 105 | .clone() 106 | .take(k_var + 1) 107 | .enumerate() 108 | .map(|(k, complex)| { 109 | let a = (complex.re * complex.re + complex.im * complex.im).sqrt(); 110 | let p = complex.im.atan2(complex.re); 111 | a * ((2.0 * PI * k as f32 * n as f32 / size) + p).cos() / size 112 | }) 113 | .sum::() 114 | }) 115 | } 116 | 117 | pub fn convolution_sum(x: I) -> impl Iterator + Clone 118 | where 119 | I: Iterator 120 | + core::iter::ExactSizeIterator 121 | + core::iter::DoubleEndedIterator 122 | + Clone, 123 | { 124 | (0..x.len()).map(move |y_n| { 125 | x.clone() 126 | .take(y_n + 1) 127 | .rev() 128 | .zip(H.iter()) 129 | .map(|(exx, h)| h * exx) 130 | .sum() 131 | }) 132 | } 133 | -------------------------------------------------------------------------------- /lab4-native/examples/4_14_linear_phase_calculations.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining the linear phase property of digital 2 | //! filters. Here we have a low-pass filter represented by h array. First its 3 | //! FFT is calculated using the arm_cfft_f32 function. Then the magnitude and 4 | //! phase of the FFT are stored in Mag and Phase arrays. 5 | //! 6 | //! Runs entirely locally without hardware. Rounding might be different than on 7 | //! device. Except for when printing you must be vigilent to not become reliant 8 | //! on any std tools that can't otherwise port over to no_std without alloc. 9 | //! 10 | //! `cargo run --example 4_14_linear_phase_calculations` 11 | 12 | use core::f32::consts::PI; 13 | use lab4::{display, Shape}; 14 | use microfft::Complex32; 15 | 16 | use microfft::complex::cfft_64 as cfft; 17 | const N: usize = 64; 18 | 19 | fn main() { 20 | // Complex impulse response of filter 21 | let mut dtfsecoef: heapless::Vec = H 22 | .iter() 23 | .cloned() 24 | .map(|h| Complex32 { re: h, im: 0.0 }) 25 | .collect(); 26 | 27 | // SAFETY microfft now only accepts arrays instead of slices to avoid runtime errors 28 | // Thats not great for us. However we can cheat since our slice into an array because 29 | // "The layout of a slice [T] of length N is the same as that of a [T; N] array." 30 | // https://rust-lang.github.io/unsafe-code-guidelines/layout/arrays-and-slices.html 31 | // this goes away when something like heapless vec is in standard library 32 | // https://github.com/rust-lang/rfcs/pull/2990 33 | unsafe { 34 | let ptr = &mut *(dtfsecoef.as_mut_ptr() as *mut [Complex32; N]); 35 | 36 | // Coefficient calculation with CFFT function 37 | // well use microfft uses an in place Radix-2 FFT 38 | // it re-returns our array in case we were going to chain calls, throw it away 39 | let _ = cfft(ptr); 40 | } 41 | 42 | // Magnitude calculation 43 | let mag: heapless::Vec = dtfsecoef 44 | .iter() 45 | .map(|complex| (complex.re * complex.re + complex.im * complex.im).sqrt()) 46 | .collect(); 47 | display("mag", Shape::Line, mag.iter().cloned()); 48 | 49 | let phase = dtfsecoef 50 | .iter() 51 | .cloned() 52 | .map(|complex| complex.re.atan2(complex.im)); 53 | 54 | // not sure why yet, but this is how they display in the matlab file 55 | let phase_graph = phase 56 | .clone() 57 | .enumerate() 58 | .map(|(i, phase)| if i < 33 { phase } else { phase - PI }); 59 | 60 | display("phase", Shape::Line, phase_graph.clone()); 61 | } 62 | 63 | // linear_phase_FIR_coefficients 64 | #[rustfmt::skip] 65 | static H: &[f32] = &[ 66 | 0.002_110_571_8, 0.003_037_402_2, 0.004_010_573, 0.005_026_416_4, 0.006_080_887_7, 67 | 0.007_169_586_6, 0.008_287_783, 0.009_430_443, 0.010_592_262, 0.011_767_695, 68 | 0.012_950_993, 0.014_136_244, 0.015_317_405, 0.016_488_347, 0.017_642_902, 69 | 0.018774895, 0.019_878_196, 0.020_946_754, 0.021_974_655, 0.022_956_148, 70 | 0.023_885_697, 0.024_758_019, 0.025_568_118, 0.026_311_33, 0.026_983_349, 71 | 0.027_580_261, 0.028_098_583, 0.028_535_27, 0.028_887_754, 0.029_153_956, 72 | 0.029_332_304, 0.029_421_745, 0.029_421_745, 0.029_332_304, 0.029_153_956, 73 | 0.028_887_754, 0.028_535_27, 0.028_098_583, 0.027_580_261, 0.026_983_349, 74 | 0.026_311_33, 0.025_568_118, 0.024_758_019, 0.023_885_697, 0.022_956_148, 75 | 0.021_974_655, 0.020_946_754, 0.019_878_196, 0.018774895, 0.017_642_902, 76 | 0.016_488_347, 0.015_317_405, 0.014_136_244, 0.012_950_993, 0.011_767_695, 77 | 0.010_592_262, 0.009_430_443, 0.008_287_783, 0.007_169_586_6, 0.006_080_887_7, 78 | 0.005_026_416_4, 0.004_010_573, 0.003_037_402_2, 0.002_110_571_8 79 | ]; 80 | -------------------------------------------------------------------------------- /lab4-native/examples/4_15_linear_phase_calculations.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining the linear phase property of digital 2 | //! filters. Here we have a low-pass filter represented by h array. First its 3 | //! FFT is calculated using the arm_cfft_f32 function. Then the magnitude and 4 | //! phase of the FFT are stored in Mag and Phase arrays. 5 | //! 6 | //! Runs entirely locally without hardware. Rounding might be different than on 7 | //! device. Except for when printing you must be vigilent to not become reliant 8 | //! on any std tools that can't otherwise port over to no_std without alloc. 9 | //! 10 | //! `cargo run --example 4_15_linear_phase_calculations` 11 | 12 | use lab4::{display, Shape}; 13 | use microfft::Complex32; 14 | 15 | use microfft::complex::cfft_64 as cfft; 16 | const N: usize = 64; 17 | 18 | fn main() { 19 | // Complex impulse response of filter 20 | let mut dtfsecoef: heapless::Vec = H 21 | .iter() 22 | .cloned() 23 | .map(|h| Complex32 { re: h, im: 0.0 }) 24 | .collect(); 25 | 26 | // SAFETY microfft now only accepts arrays instead of slices to avoid runtime errors 27 | // Thats not great for us. However we can cheat since our slice into an array because 28 | // "The layout of a slice [T] of length N is the same as that of a [T; N] array." 29 | // https://rust-lang.github.io/unsafe-code-guidelines/layout/arrays-and-slices.html 30 | // this goes away when something like heapless vec is in standard library 31 | // https://github.com/rust-lang/rfcs/pull/2990 32 | unsafe { 33 | let ptr = &mut *(dtfsecoef.as_mut_ptr() as *mut [Complex32; N]); 34 | 35 | // Coefficient calculation with CFFT function 36 | // well use microfft uses an in place Radix-2 FFT 37 | // it re-returns our array in case we were going to chain calls, throw it away 38 | let _ = cfft(ptr); 39 | } 40 | 41 | // Magnitude calculation 42 | let mag: heapless::Vec = dtfsecoef 43 | .iter() 44 | .map(|complex| (complex.re * complex.re + complex.im * complex.im).sqrt()) 45 | .collect(); 46 | display("mag", Shape::Line, mag.iter().cloned()); 47 | 48 | let phase = dtfsecoef 49 | .iter() 50 | .cloned() 51 | .map(|complex| complex.re.atan2(complex.im)); 52 | 53 | display("phase", Shape::Line, phase.clone()); 54 | } 55 | 56 | // FIR_lpf_coefficients for 4_15 57 | static H: &[f32] = &[ 58 | 0.002044, 0.007806, 0.014554, 0.020018, 0.024374, 0.027780, 0.030370, 0.032264, 0.033568, 59 | 0.034372, 0.034757, 0.034791, 0.034534, 0.034040, 0.033353, 0.032511, 0.031549, 0.030496, 60 | 0.029375, 0.028207, 0.027010, 0.025800, 0.024587, 0.023383, 0.022195, 0.021031, 0.019896, 61 | 0.018795, 0.017730, 0.016703, 0.015718, 0.014774, 0.013872, 0.013013, 0.012196, 0.011420, 62 | 0.010684, 0.009989, 0.009331, 0.008711, 0.008127, 0.007577, 0.007061, 0.006575, 0.006120, 63 | 0.005693, 0.005294, 0.004920, 0.004570, 0.004244, 0.003939, 0.003655, 0.003389, 0.003142, 64 | 0.002912, 0.002698, 0.002499, 0.002313, 0.002141, 0.001981, 0.001833, 0.001695, 0.001567, 65 | 0.001448, 66 | ]; 67 | -------------------------------------------------------------------------------- /lab4-native/examples/4_1_dft_calculations.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining the DFT operation using standard math 2 | //! functions. Here, we have a digital input signal as the sum of two sinusoids 3 | //! with different frequencies. The complex form of this signal is represented 4 | //! with s_complex array, the frequency component of this signal is found by the 5 | //! DFT function. Real and imaginary parts of the obtained DFT are represented 6 | //! with XR and XI arrays. The magnitude of DFT is kept in the Mag array. 7 | //! 8 | //! Runs entirely locally without hardware. Rounding might be different than on 9 | //! device. Except for when printing you must be vigilent to not become reliant 10 | //! on any std tools that can't otherwise port over to no_std without alloc. 11 | //! 12 | //! `cargo run --example 4_1_dft_calculations` 13 | 14 | use core::f32::consts::PI; 15 | use lab4::{display, Shape}; 16 | 17 | const N: usize = 256; 18 | 19 | const W1: f32 = core::f32::consts::PI / 128.0; 20 | const W2: f32 = core::f32::consts::PI / 4.0; 21 | // const W2: f32 = core::f32::consts::PI / 5.0; 22 | 23 | fn main() { 24 | // Complex sum of sinusoidal signals 25 | let s1 = (0..N).map(|val| (W1 * val as f32).sin()); 26 | let s2 = (0..N).map(|val| (W2 * val as f32).sin()); 27 | let s = s1.zip(s2).map(|(ess1, ess2)| ess1 + ess2); 28 | 29 | // map it to real, leave im blank well fill in with dft 30 | let dtfsecoef = s.map(|f| Complex32 { re: f, im: 0.0 }); 31 | 32 | let dft: heapless::Vec = dft(dtfsecoef).collect(); 33 | 34 | let re: heapless::Vec = dft.iter().map(|complex| complex.re).collect(); 35 | display("re", Shape::Line, re.iter().cloned()); 36 | 37 | let im: heapless::Vec = dft.iter().map(|complex| complex.im).collect(); 38 | display("im", Shape::Line, im.iter().cloned()); 39 | 40 | //Magnitude calculation 41 | let mag: heapless::Vec = dft 42 | .iter() 43 | .map(|complex| (complex.re * complex.re + complex.im * complex.im).sqrt()) 44 | .collect(); 45 | display("mag", Shape::Line, mag.iter().cloned()); 46 | } 47 | 48 | fn dft + Clone>(input: I) -> impl Iterator { 49 | let size = N as f32; 50 | (0..N).map(move |k| { 51 | input 52 | .clone() 53 | .enumerate() 54 | .fold((0f32, 0f32), |(mut sum_re, mut sum_im), (n, complex)| { 55 | let n = n as f32; 56 | sum_re += complex.re * (2.0 * PI * k as f32 * n / size).cos() 57 | + complex.im * (2.0 * PI * k as f32 * n / size).sin(); 58 | sum_im += -complex.im * (2.0 * PI * k as f32 * n / size).cos() 59 | + complex.re * (2.0 * PI * k as f32 * n / size).sin(); 60 | 61 | (sum_re, sum_im) 62 | }) 63 | .into() 64 | }) 65 | } 66 | 67 | struct Complex32 { 68 | re: f32, 69 | im: f32, 70 | } 71 | 72 | impl From<(f32, f32)> for Complex32 { 73 | fn from(incoming: (f32, f32)) -> Self { 74 | Complex32 { 75 | re: incoming.0, 76 | im: incoming.1, 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lab4-native/examples/4_8_dtfse_calculations.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining the DTFSE operation. Here, we have a 2 | //! periodic square signal. The complex form of this signal is represented with 3 | //! s_complex array. DTFSE coefficients are calculated, then, the signal is 4 | //! approximated with the DTFSE function. This function returns its output in 5 | //! real form because original signal has only real parts in this example. The 6 | //! result is kept in the y_real array. 7 | //! 8 | //! Runs entirely locally without hardware. Rounding might be different than on 9 | //! device. Except for when printing you must be vigilent to not become reliant 10 | //! on any std tools that can't otherwise port over to no_std without alloc. 11 | //! 12 | //! `cargo run --example 4_8_dtfse_calculations` 13 | 14 | use core::f32::consts::PI; 15 | use lab4::{display, Shape}; 16 | use microfft::Complex32; 17 | 18 | use microfft::complex::cfft_16 as cfft; 19 | const N: usize = 16; 20 | 21 | fn main() { 22 | //square signal 23 | let square = (0..N).map(|idx| if idx < N / 2 { 1.0 } else { 0.0 }); 24 | display("square", Shape::Line, square.clone()); 25 | 26 | //map it to real, leave im blank well fill in with cfft 27 | let mut dtfsecoef: heapless::Vec = 28 | square.map(|f| Complex32 { re: f, im: 0.0 }).collect(); 29 | 30 | // SAFETY microfft now only accepts arrays instead of slices to avoid runtime errors 31 | // Thats not great for us. However we can cheat since our slice into an array because 32 | // "The layout of a slice [T] of length N is the same as that of a [T; N] array." 33 | // https://rust-lang.github.io/unsafe-code-guidelines/layout/arrays-and-slices.html 34 | // this goes away when something like heapless vec is in standard library 35 | // https://github.com/rust-lang/rfcs/pull/2990 36 | unsafe { 37 | let ptr = &mut *(dtfsecoef.as_mut_ptr() as *mut [Complex32; N]); 38 | 39 | // Coefficient calculation with CFFT function 40 | // well use microfft uses an in place Radix-2 FFT 41 | // it re-returns our array in case we were going to chain calls, throw it away 42 | let _ = cfft(ptr); 43 | } 44 | 45 | println!("dtfsecoef: {:?}", &dtfsecoef); 46 | 47 | //dtfse to reclaim our original signal, note this is a bad approximation for our square wave 48 | let y_real: heapless::Vec = dtfse(dtfsecoef.iter().cloned(), 1).collect(); 49 | display("y_real 1", Shape::Line, y_real.iter().cloned()); 50 | 51 | //a bit better 52 | let y_real: heapless::Vec = dtfse(dtfsecoef.iter().cloned(), 5).collect(); 53 | display("y_real 5", Shape::Line, y_real.iter().cloned()); 54 | 55 | //good 56 | let y_real: heapless::Vec = dtfse(dtfsecoef.iter().cloned(), 15).collect(); 57 | display("y_real 15", Shape::Line, y_real.iter().cloned()); 58 | } 59 | 60 | fn dtfse + Clone>( 61 | coeff: I, 62 | k_var: usize, 63 | ) -> impl Iterator { 64 | let size = N as f32; 65 | (0..N).map(move |n| { 66 | coeff 67 | .clone() 68 | .take(k_var + 1) 69 | .enumerate() 70 | .map(|(k, complex)| { 71 | let a = (complex.re * complex.re + complex.im * complex.im).sqrt(); 72 | let p = complex.im.atan2(complex.re); 73 | a * ((2.0 * PI * k as f32 * n as f32 / size) + p).cos() / size 74 | }) 75 | .sum::() 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /lab4-native/examples/4_9.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining the DTFSE operation. Here, we have a 2 | //! periodic square signal. The complex form of this signal is represented with 3 | //! s_complex array. DTFSE coefficients are calculated, then, the signal is 4 | //! approximated with the DTFSE function. This function returns its output in 5 | //! real form because original signal has only real parts in this example. The 6 | //! result is kept in the y_real array. 7 | //! 8 | //! Runs entirely locally without hardware. Rounding might be different than on 9 | //! device. Except for when printing you must be vigilent to not become reliant 10 | //! on any std tools that can't otherwise port over to no_std without alloc. 11 | //! 12 | //! `cargo run --example 4_9` 13 | 14 | use core::f32::consts::PI; 15 | use lab4::{display, Shape}; 16 | use microfft::Complex32; 17 | 18 | use microfft::complex::cfft_16 as cfft; 19 | const N: usize = 16; 20 | 21 | const TRIANGLE_AMPLITUDE: f32 = 1.5; 22 | const TRIANGLE_PERIOD: usize = 16; 23 | 24 | fn main() { 25 | // Collecting to turn the Cycle into a clean iterator for our naive display fn 26 | let triangle: heapless::Vec = (0..TRIANGLE_PERIOD) 27 | .map(|n| { 28 | let period = TRIANGLE_PERIOD as f32; 29 | 30 | if n < (TRIANGLE_PERIOD / 2) { 31 | (2.0 * TRIANGLE_AMPLITUDE / (period / 2.0)) * n as f32 - TRIANGLE_AMPLITUDE 32 | } else { 33 | -(2.0 * TRIANGLE_AMPLITUDE / (period / 2.0)) * (n as f32 - period / 2.0) 34 | + TRIANGLE_AMPLITUDE 35 | } 36 | }) 37 | .cycle() 38 | .take(N) 39 | .collect(); 40 | display("triangle signal", Shape::Line, triangle.iter().cloned()); 41 | 42 | //map it to real, leave im blank well fill in with cfft 43 | let mut dtfsecoef: heapless::Vec = triangle 44 | .iter() 45 | .cloned() 46 | .map(|f| Complex32 { re: f, im: 0.0 }) 47 | .collect(); 48 | 49 | // SAFETY microfft now only accepts arrays instead of slices to avoid runtime errors 50 | // Thats not great for us. However we can cheat since our slice into an array because 51 | // "The layout of a slice [T] of length N is the same as that of a [T; N] array." 52 | // https://rust-lang.github.io/unsafe-code-guidelines/layout/arrays-and-slices.html 53 | // this goes away when something like heapless vec is in standard library 54 | // https://github.com/rust-lang/rfcs/pull/2990 55 | unsafe { 56 | let ptr = &mut *(dtfsecoef.as_mut_ptr() as *mut [Complex32; N]); 57 | 58 | // Coefficient calculation with CFFT function 59 | // well use microfft uses an in place Radix-2 FFT 60 | // it re-returns our array in case we were going to chain calls, throw it away 61 | let _ = cfft(ptr); 62 | } 63 | println!("dtfsecoef: {:?}", &dtfsecoef); 64 | 65 | //dtfse to reclaim our original signal, note this is a bad approximation for our square wave 66 | let y_real: heapless::Vec = dtfse(dtfsecoef.iter().cloned(), 2).collect(); 67 | display("y_real 2", Shape::Line, y_real.iter().cloned()); 68 | 69 | //a bit better 70 | let y_real: heapless::Vec = dtfse(dtfsecoef.iter().cloned(), 5).collect(); 71 | display("y_real 5", Shape::Line, y_real.iter().cloned()); 72 | 73 | //good 74 | let y_real: heapless::Vec = dtfse(dtfsecoef.iter().cloned(), 8).collect(); 75 | display("y_real 8", Shape::Line, y_real.iter().cloned()); 76 | 77 | //good 78 | let y_real: heapless::Vec = dtfse(dtfsecoef.iter().cloned(), 15).collect(); 79 | display("y_real 15", Shape::Line, y_real.iter().cloned()); 80 | } 81 | 82 | fn dtfse + Clone>( 83 | coeff: I, 84 | k_var: usize, 85 | ) -> impl Iterator { 86 | let size = N as f32; 87 | (0..N).map(move |n| { 88 | coeff 89 | .clone() 90 | .take(k_var + 1) 91 | .enumerate() 92 | .map(|(k, complex)| { 93 | let a = (complex.re * complex.re + complex.im * complex.im).sqrt(); 94 | let p = complex.im.atan2(complex.re); 95 | a * ((2.0 * PI * k as f32 * n as f32 / size) + p).cos() / size 96 | }) 97 | .sum::() 98 | }) 99 | } 100 | -------------------------------------------------------------------------------- /lab4-native/src/lib.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use textplots::{Chart, Plot}; 3 | 4 | #[non_exhaustive] 5 | pub enum Shape { 6 | Line, 7 | Points, 8 | } 9 | 10 | pub fn display(name: &str, shape: Shape, input: I) 11 | where 12 | I: IntoIterator, 13 | ::IntoIter: Clone, 14 | ::Item: Into + std::fmt::Debug, 15 | { 16 | let i = input.into_iter(); 17 | let display: Vec<(f32, f32)> = i 18 | .clone() 19 | .enumerate() 20 | .map(|(n, y)| (n as f32, y.into())) 21 | .collect(); 22 | println!("{:?}: {:.4?}", name, i.format(", ")); 23 | 24 | let data = match shape { 25 | Shape::Line => textplots::Shape::Lines(&display), 26 | Shape::Points => textplots::Shape::Points(&display), 27 | }; 28 | 29 | // Continuous requires to be in a fn pointer closure which cant capture any 30 | // external data so not useful without lots of code duplication. 31 | // Lines occasionally looks good.. but mostly bad 32 | Chart::new(120, 60, 0.0, display.len() as f32) 33 | .lineplot(&data) 34 | .display(); 35 | } 36 | -------------------------------------------------------------------------------- /lab4/.cargo/config: -------------------------------------------------------------------------------- 1 | # cargo run --example whatever 2 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 3 | 4 | # openocd 5 | #runner = "arm-none-eabi-gdb -x openocd.gdb" 6 | 7 | # probe-run 8 | runner = "probe-run --chip STM32F407VGTx" 9 | 10 | rustflags = [ 11 | "-C", "link-arg=-Tlink.x", 12 | ] 13 | 14 | [build] 15 | target = "thumbv7em-none-eabihf" 16 | -------------------------------------------------------------------------------- /lab4/.embed.toml: -------------------------------------------------------------------------------- 1 | [default.rtt] 2 | enabled = true 3 | 4 | [default.general] 5 | chip = "STM32F407VGTx" 6 | -------------------------------------------------------------------------------- /lab4/.vscode/.cortex-debug.peripherals.state.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /lab4/.vscode/.cortex-debug.registers.state.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /lab4/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": [ 6 | "rust-lang.rust", 7 | "marus25.cortex-debug", 8 | ], 9 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 10 | "unwantedRecommendations": [] 11 | } -------------------------------------------------------------------------------- /lab4/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "cortex-debug", 6 | "request": "launch", 7 | "servertype": "openocd", 8 | "cwd": "${workspaceRoot}", 9 | /* dont forget to select whichever example you are debugging*/ 10 | "executable": "./target/thumbv7em-none-eabihf/debug/examples/4_1_dft_calculations", 11 | "preLaunchTask": "rust: cargo build examples", 12 | "name": "Debug (OpenOCD)", 13 | "device": "STM32F407VGT6", 14 | "configFiles": [ 15 | "board/stm32f4discovery.cfg" 16 | ], 17 | "runToMain": true, 18 | "gdbpath": "gdb-multiarch", 19 | "svdFile": "${workspaceRoot}/.vscode/STM32F407.svd" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /lab4/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.target": "thumbv7em-none-eabihf", 3 | "rust-analyzer.checkOnSave.allTargets": false, 4 | "rust-analyzer.checkOnSave.extraArgs": [ 5 | "--examples" 6 | ], 7 | "files.watcherExclude": { 8 | "**/.git/objects/**": true, 9 | "**/.git/subtree-cache/**": true, 10 | "**/target/**": true 11 | } 12 | } -------------------------------------------------------------------------------- /lab4/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "cargo", 6 | "command": "build", 7 | "args": [ 8 | "--examples", 9 | ], 10 | "problemMatcher": [ 11 | "$rustc" 12 | ], 13 | "group": "build", 14 | "label": "rust: cargo build examples" 15 | }, 16 | { 17 | "type": "cargo", 18 | "command": "build", 19 | "problemMatcher": [ 20 | "$rustc" 21 | ], 22 | "group": "build", 23 | "label": "rust: cargo build" 24 | }, 25 | { 26 | "type": "cargo", 27 | "command": "clean", 28 | "problemMatcher": [ 29 | "$rustc" 30 | ], 31 | "group": "build", 32 | "label": "rust: cargo clean" 33 | }, 34 | ] 35 | } -------------------------------------------------------------------------------- /lab4/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lab4" 3 | version = "0.1.0" 4 | authors = ["Jacob Rosenthal "] 5 | edition = "2018" 6 | resolver = "2" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | [dependencies] 10 | cortex-m = "0.7.2" 11 | cortex-m-rt = "0.6.12" 12 | stm32f4xx-hal = { version = "0.9.0", features = ["stm32f407", "rt"] } 13 | panic_break = { path = "../panic_break" } 14 | panic-halt = "0.2.0" 15 | rtt-target = { version = "0.3.1", features = ["cortex-m"] } 16 | micromath = "2.0.0" 17 | microfft = "0.4.0" 18 | itertools = { version = "0.10.0", default-features = false } 19 | heapless = { version = "0.7.1" } 20 | lis3dsh = { git = "https://github.com/jacobrosenthal/lis3dsh-rs", branch = "bdu" } 21 | cty = "0.2.1" 22 | cmsis-dsp-sys = "0.3.1" 23 | 24 | [dependencies.embedded-hal] 25 | features = ["unproven"] 26 | version = "0.2.5" 27 | 28 | # for cargo flash 29 | [package.metadata] 30 | chip = "STM32F407VGTx" 31 | 32 | [profile.dev] 33 | codegen-units = 1 34 | debug = 1 35 | debug-assertions = true # ! 36 | incremental = false 37 | lto = "fat" 38 | opt-level = 'z' # ! 39 | overflow-checks = false 40 | 41 | [profile.release] 42 | codegen-units = 1 43 | debug = 1 44 | debug-assertions = false 45 | incremental = false 46 | lto = "fat" 47 | opt-level = 3 48 | overflow-checks = false 49 | -------------------------------------------------------------------------------- /lab4/examples/4_11_stft_accelerometer.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining the STFT operation on real-world 2 | //! signals. Here we first sample the acceleromater data. Sampling period is set 3 | //! as 10 milliseconds. We also generate a Hamming window. These signals are 4 | //! represented with x and v arrays in main.c file respectively. The input 5 | //! signal is divided into subwindows and FFT of each subwindow is calculated by 6 | //! the STFT function. The result is stored in the XST array. 7 | //! 8 | //! Requires `cargo install probe-run` 9 | //! `cargo run --release --example 4_11_stft_accelerometer` 10 | //! 11 | //! Note: This is currently stack overflowing with Window larger than 16 12 | 13 | #![no_std] 14 | #![no_main] 15 | 16 | use panic_break as _; 17 | use stm32f4xx_hal as hal; 18 | 19 | use cmsis_dsp_sys::{arm_cfft_f32, arm_cmplx_mag_f32}; 20 | use core::f32::consts::PI; 21 | use cty::uint32_t; 22 | use hal::{prelude::*, spi, stm32}; 23 | use itertools::Itertools; 24 | use lis3dsh::{accelerometer::RawAccelerometer, Lis3dsh}; 25 | use micromath::F32Ext; 26 | use rtt_target::{rprintln, rtt_init_print}; 27 | 28 | use cmsis_dsp_sys::arm_cfft_sR_f32_len16 as arm_cfft_sR_f32; 29 | const WINDOW: usize = 16; 30 | const WINDOWCOMPLEX: usize = WINDOW * 2; 31 | 32 | const N: usize = 1024; 33 | 34 | #[cortex_m_rt::entry] 35 | fn main() -> ! { 36 | rtt_init_print!(BlockIfFull, 128); 37 | 38 | let dp = stm32::Peripherals::take().unwrap(); 39 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 40 | 41 | // Set up the system clock. 42 | let rcc = dp.RCC.constrain(); 43 | 44 | let clocks = rcc 45 | .cfgr 46 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 47 | .sysclk(168.mhz()) 48 | .freeze(); 49 | 50 | let mut delay = hal::delay::Delay::new(cp.SYST, clocks); 51 | 52 | let gpioa = dp.GPIOA.split(); 53 | let gpioe = dp.GPIOE.split(); 54 | 55 | let sck = gpioa.pa5.into_alternate_af5().internal_pull_up(false); 56 | let miso = gpioa.pa6.into_alternate_af5().internal_pull_up(false); 57 | let mosi = gpioa.pa7.into_alternate_af5().internal_pull_up(false); 58 | 59 | let spi = spi::Spi::spi1( 60 | dp.SPI1, 61 | (sck, miso, mosi), 62 | spi::Mode { 63 | polarity: spi::Polarity::IdleLow, 64 | phase: spi::Phase::CaptureOnFirstTransition, 65 | }, 66 | 10.mhz().into(), 67 | clocks, 68 | ); 69 | 70 | let chip_select = gpioe.pe3.into_push_pull_output(); 71 | let mut lis3dsh = Lis3dsh::new_spi(spi, chip_select); 72 | lis3dsh.init(&mut delay).unwrap(); 73 | 74 | rprintln!("reading accel"); 75 | 76 | // dont love the idea of delaying in an iterator ... 77 | let accel: heapless::Vec = (0..N) 78 | .map(|_| { 79 | while !lis3dsh.is_data_ready().unwrap() {} 80 | lis3dsh.accel_raw().unwrap().x as f32 81 | }) 82 | .collect(); 83 | 84 | rprintln!("computing"); 85 | 86 | let hamming = (0..WINDOW).map(|m| 0.54 - 0.46 * (2.0 * PI * m as f32 / WINDOW as f32).cos()); 87 | 88 | // get 64 input at a time, overlapping 32 89 | // windowing is easier to do on slices 90 | let overlapping_chirp_windows = Windows { 91 | v: &accel, 92 | size: WINDOW, 93 | inc: WINDOW / 2, 94 | }; 95 | 96 | let mut xst: heapless::Vec<[f32; WINDOW], N> = heapless::Vec::new(); 97 | 98 | let mut mag = [0f32; WINDOW]; 99 | 100 | for chirp_win in overlapping_chirp_windows { 101 | // 64-0=64 of input to 64-64=0, so input * chirp.rev 102 | let mut dtfsecoef: heapless::Vec = hamming 103 | .clone() 104 | .zip(chirp_win.iter().rev()) 105 | .map(|(v, x)| v * x) 106 | .interleave_shortest(core::iter::repeat(0.0)) 107 | .collect(); 108 | 109 | unsafe { 110 | //Finding the FFT of window 111 | arm_cfft_f32(&arm_cfft_sR_f32, dtfsecoef.as_mut_ptr(), 0, 1); 112 | arm_cmplx_mag_f32(dtfsecoef.as_ptr(), mag.as_mut_ptr(), WINDOW as uint32_t); 113 | } 114 | 115 | xst.push(mag).ok(); 116 | } 117 | 118 | rprintln!("xst: {:?}", xst); 119 | 120 | // signal to probe-run to exit 121 | loop { 122 | cortex_m::asm::bkpt() 123 | } 124 | } 125 | 126 | pub struct Windows<'a, T: 'a> { 127 | v: &'a [T], 128 | size: usize, 129 | inc: usize, 130 | } 131 | 132 | impl<'a, T> Iterator for Windows<'a, T> { 133 | type Item = &'a [T]; 134 | 135 | #[inline] 136 | fn next(&mut self) -> Option<&'a [T]> { 137 | if self.size > self.v.len() { 138 | None 139 | } else { 140 | let ret = Some(&self.v[..self.size]); 141 | self.v = &self.v[self.inc..]; 142 | ret 143 | } 144 | } 145 | } 146 | 147 | //C needs access to a sqrt fn, lets use micromath 148 | #[no_mangle] 149 | pub extern "C" fn sqrtf(x: f32) -> f32 { 150 | x.sqrt() 151 | } 152 | -------------------------------------------------------------------------------- /lab4/examples/4_11_stft_accelerometer_microfft.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining the STFT operation on real-world 2 | //! signals. Here we first sample the acceleromater data. Sampling period is set 3 | //! as 10 milliseconds. We also generate a Hamming window. These signals are 4 | //! represented with x and v arrays in main.c file respectively. The input 5 | //! signal is divided into subwindows and FFT of each subwindow is calculated by 6 | //! the STFT function. The result is stored in the XST array. 7 | //! 8 | //! Requires `cargo install probe-run` 9 | //! `cargo run --release --example 4_11_stft_accelerometer_microfft` 10 | //! 11 | //! Note: This is currently stack overflowing with Window larger than 16 12 | 13 | #![no_std] 14 | #![no_main] 15 | 16 | use panic_break as _; 17 | use stm32f4xx_hal as hal; 18 | 19 | use core::f32::consts::PI; 20 | use hal::{prelude::*, spi, stm32}; 21 | use lis3dsh::{accelerometer::RawAccelerometer, Lis3dsh}; 22 | use microfft::Complex32; 23 | use micromath::F32Ext; 24 | use rtt_target::{rprintln, rtt_init_print}; 25 | 26 | use microfft::complex::cfft_16 as cfft; 27 | const WINDOW: usize = 16; 28 | 29 | const N: usize = 1024; 30 | const NDIV2: usize = N / 2; 31 | 32 | #[cortex_m_rt::entry] 33 | fn main() -> ! { 34 | rtt_init_print!(BlockIfFull, 128); 35 | 36 | let dp = stm32::Peripherals::take().unwrap(); 37 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 38 | 39 | // Set up the system clock. 40 | let rcc = dp.RCC.constrain(); 41 | 42 | let clocks = rcc 43 | .cfgr 44 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 45 | .sysclk(168.mhz()) 46 | .freeze(); 47 | 48 | let mut delay = hal::delay::Delay::new(cp.SYST, clocks); 49 | 50 | let gpioa = dp.GPIOA.split(); 51 | let gpioe = dp.GPIOE.split(); 52 | 53 | let sck = gpioa.pa5.into_alternate_af5().internal_pull_up(false); 54 | let miso = gpioa.pa6.into_alternate_af5().internal_pull_up(false); 55 | let mosi = gpioa.pa7.into_alternate_af5().internal_pull_up(false); 56 | 57 | let spi = spi::Spi::spi1( 58 | dp.SPI1, 59 | (sck, miso, mosi), 60 | spi::Mode { 61 | polarity: spi::Polarity::IdleLow, 62 | phase: spi::Phase::CaptureOnFirstTransition, 63 | }, 64 | 10.mhz().into(), 65 | clocks, 66 | ); 67 | 68 | let chip_select = gpioe.pe3.into_push_pull_output(); 69 | let mut lis3dsh = Lis3dsh::new_spi(spi, chip_select); 70 | lis3dsh.init(&mut delay).unwrap(); 71 | 72 | rprintln!("reading accel"); 73 | 74 | // dont love the idea of delaying in an iterator ... 75 | let accel: heapless::Vec = (0..N) 76 | .map(|_| { 77 | while !lis3dsh.is_data_ready().unwrap() {} 78 | let dat = lis3dsh.accel_raw().unwrap(); 79 | dat[0] as f32 80 | }) 81 | .collect(); 82 | 83 | rprintln!("computing"); 84 | 85 | let hamming = (0..WINDOW).map(|m| 0.54 - 0.46 * (2.0 * PI * m as f32 / WINDOW as f32).cos()); 86 | 87 | // get 64 input at a time, overlapping 32 88 | // windowing is easier to do on slices 89 | let overlapping_chirp_windows = Windows { 90 | v: &accel, 91 | size: WINDOW, 92 | inc: WINDOW / 2, 93 | }; 94 | 95 | let xst: heapless::Vec<_, NDIV2> = overlapping_chirp_windows 96 | .map(|chirp_win| { 97 | let mut dtfsecoef: heapless::Vec = hamming 98 | .clone() 99 | .zip(chirp_win.iter().rev()) 100 | .map(|(v, x)| Complex32 { re: v * x, im: 0.0 }) 101 | .collect(); 102 | 103 | // SAFETY microfft now only accepts arrays instead of slices to avoid runtime errors 104 | // Thats not great for us. However we can cheat since our slice into an array because 105 | // "The layout of a slice [T] of length N is the same as that of a [T; N] array." 106 | // https://rust-lang.github.io/unsafe-code-guidelines/layout/arrays-and-slices.html 107 | // this goes away when something like heapless vec is in standard library 108 | // https://github.com/rust-lang/rfcs/pull/2990 109 | unsafe { 110 | let ptr = &mut *(dtfsecoef.as_mut_ptr() as *mut [Complex32; WINDOW]); 111 | 112 | // Coefficient calculation with CFFT function 113 | // well use microfft uses an in place Radix-2 FFT 114 | // it re-returns our array in case we were going to chain calls, throw it away 115 | let _ = cfft(ptr); 116 | } 117 | 118 | // Magnitude calculation 119 | let mag: heapless::Vec<_, WINDOW> = dtfsecoef 120 | .iter() 121 | .map(|complex| (complex.re * complex.re + complex.im * complex.im).sqrt()) 122 | .collect(); 123 | mag 124 | }) 125 | .collect(); 126 | 127 | rprintln!("xst: {:?}", xst); 128 | 129 | // signal to probe-run to exit 130 | loop { 131 | cortex_m::asm::bkpt() 132 | } 133 | } 134 | 135 | pub struct Windows<'a, T: 'a> { 136 | v: &'a [T], 137 | size: usize, 138 | inc: usize, 139 | } 140 | 141 | impl<'a, T> Iterator for Windows<'a, T> { 142 | type Item = &'a [T]; 143 | 144 | #[inline] 145 | fn next(&mut self) -> Option<&'a [T]> { 146 | if self.size > self.v.len() { 147 | None 148 | } else { 149 | let ret = Some(&self.v[..self.size]); 150 | self.v = &self.v[self.inc..]; 151 | ret 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /lab4/examples/4_13_fif_calculations.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining filtering in frequency domain. Here, we 2 | //! have a digital input signal as the sum of two sinusoids with different 3 | //! frequencies. The complex form of this signal is represented with s_complex 4 | //! array in main.c file. Also we have a digital filter represented with h array 5 | //! given in FIR_lpf_coefficients.h file. 6 | //! 7 | //! Requires `cargo install probe-run` 8 | //! `cargo run --release --example 4_13_fif_calculations` 9 | 10 | #![no_std] 11 | #![no_main] 12 | 13 | use panic_break as _; 14 | use stm32f4xx_hal as hal; 15 | 16 | use cmsis_dsp_sys::{arm_cfft_f32, arm_cmplx_mult_cmplx_f32}; 17 | use cty::uint32_t; 18 | use hal::{dwt::ClockDuration, dwt::DwtExt, prelude::*, stm32}; 19 | use itertools::Itertools; 20 | use micromath::F32Ext; 21 | use rtt_target::{rprintln, rtt_init_print}; 22 | 23 | use cmsis_dsp_sys::arm_cfft_sR_f32_len512 as arm_cfft_sR_f32; 24 | const N: usize = 512; 25 | const NCOMPLEX: usize = N * 2; 26 | 27 | const W1: f32 = core::f32::consts::PI / 128.0; 28 | const W2: f32 = core::f32::consts::PI / 4.0; 29 | 30 | #[cortex_m_rt::entry] 31 | fn main() -> ! { 32 | rtt_init_print!(BlockIfFull, 128); 33 | 34 | let dp = stm32::Peripherals::take().unwrap(); 35 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 36 | 37 | // Set up the system clock. 38 | let rcc = dp.RCC.constrain(); 39 | 40 | let clocks = rcc 41 | .cfgr 42 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 43 | .sysclk(168.mhz()) 44 | .freeze(); 45 | 46 | // Create a delay abstraction based on DWT cycle counter 47 | let dwt = cp.DWT.constrain(cp.DCB, clocks); 48 | 49 | // Complex sum of sinusoidal signals 50 | let s1 = (0..N).map(|val| (W1 * val as f32).sin()); 51 | let s2 = (0..N).map(|val| (W2 * val as f32).sin()); 52 | 53 | //we wont use complex this time since, but just interleave the zeros for the imaginary part 54 | let mut s_complex: heapless::Vec = s1 55 | .zip(s2) 56 | .map(|(ess1, ess2)| ess1 + ess2) 57 | .interleave_shortest(core::iter::repeat(0.0)) 58 | .collect(); 59 | 60 | // Complex impulse response of filter 61 | let mut df_complex: heapless::Vec = H 62 | .iter() 63 | .cloned() 64 | .interleave_shortest(core::iter::repeat(0.0)) 65 | .chain(core::iter::repeat(0.0)) 66 | //fill rest with zeros up to N*2 67 | .take(NCOMPLEX) 68 | .collect(); 69 | 70 | // Finding the FFT of the filter 71 | unsafe { 72 | arm_cfft_f32(&arm_cfft_sR_f32, df_complex.as_mut_ptr(), 0, 1); 73 | } 74 | 75 | let mut y_complex = [0f32; N * 2]; 76 | 77 | let time: ClockDuration = dwt.measure(|| { 78 | // Finding the FFT of the input signal 79 | unsafe { 80 | arm_cfft_f32(&arm_cfft_sR_f32, s_complex.as_mut_ptr(), 0, 1); 81 | } 82 | 83 | // Filtering in the frequency domain 84 | unsafe { 85 | arm_cmplx_mult_cmplx_f32( 86 | s_complex.as_ptr(), 87 | df_complex.as_ptr(), 88 | y_complex.as_mut_ptr(), 89 | N as uint32_t, 90 | ); 91 | } 92 | 93 | // Finding the complex result in time domain 94 | unsafe { 95 | arm_cfft_f32(&arm_cfft_sR_f32, y_complex.as_mut_ptr(), 1, 1); 96 | } 97 | }); 98 | rprintln!("dft ticks: {:?}", time.as_ticks()); 99 | 100 | // signal to probe-run to exit 101 | loop { 102 | cortex_m::asm::bkpt() 103 | } 104 | } 105 | 106 | static H: &[f32] = &[ 107 | 0.002044, 0.007806, 0.014554, 0.020018, 0.024374, 0.027780, 0.030370, 0.032264, 0.033568, 108 | 0.034372, 0.034757, 0.034791, 0.034534, 0.034040, 0.033353, 0.032511, 0.031549, 0.030496, 109 | 0.029375, 0.028207, 0.027010, 0.025800, 0.024587, 0.023383, 0.022195, 0.021031, 0.019896, 110 | 0.018795, 0.017730, 0.016703, 0.015718, 0.014774, 0.013872, 0.013013, 0.012196, 0.011420, 111 | 0.010684, 0.009989, 0.009331, 0.008711, 0.008127, 0.007577, 0.007061, 0.006575, 0.006120, 112 | 0.005693, 0.005294, 0.004920, 0.004570, 0.004244, 0.003939, 0.003655, 0.003389, 0.003142, 113 | 0.002912, 0.002698, 0.002499, 0.002313, 0.002141, 0.001981, 0.001833, 0.001695, 0.001567, 114 | 0.001448, 115 | ]; 116 | 117 | //C needs access to a sqrt fn, lets use micromath 118 | #[no_mangle] 119 | pub extern "C" fn sqrtf(x: f32) -> f32 { 120 | x.sqrt() 121 | } 122 | -------------------------------------------------------------------------------- /lab4/examples/4_13_fif_calculations_microfft.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining filtering in frequency domain. Here, we 2 | //! have a digital input signal as the sum of two sinusoids with different 3 | //! frequencies. The complex form of this signal is represented with s_complex 4 | //! array in main.c file. Also we have a digital filter represented with h array 5 | //! given in FIR_lpf_coefficients.h file. 6 | //! 7 | //! Requires `cargo install probe-run` 8 | //! `cargo run --release --example 4_13_fif_calculations_microfft` 9 | 10 | #![no_std] 11 | #![no_main] 12 | 13 | use panic_break as _; 14 | use stm32f4xx_hal as hal; 15 | 16 | use core::f32::consts::PI; 17 | use hal::{dwt::ClockDuration, dwt::DwtExt, prelude::*, stm32}; 18 | use microfft::Complex32; 19 | use micromath::F32Ext; 20 | use rtt_target::{rprintln, rtt_init_print}; 21 | 22 | use microfft::complex::cfft_512 as cfft; 23 | const N: usize = 512; 24 | 25 | const W1: f32 = core::f32::consts::PI / 128.0; 26 | const W2: f32 = core::f32::consts::PI / 4.0; 27 | 28 | #[cortex_m_rt::entry] 29 | fn main() -> ! { 30 | rtt_init_print!(BlockIfFull, 128); 31 | 32 | let dp = stm32::Peripherals::take().unwrap(); 33 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 34 | 35 | // Set up the system clock. 36 | let rcc = dp.RCC.constrain(); 37 | 38 | let clocks = rcc 39 | .cfgr 40 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 41 | .sysclk(168.mhz()) 42 | .freeze(); 43 | 44 | // Create a delay abstraction based on DWT cycle counter 45 | let dwt = cp.DWT.constrain(cp.DCB, clocks); 46 | 47 | // Complex sum of sinusoidal signals 48 | let s1 = (0..N).map(|val| (W1 * val as f32).sin()); 49 | let s2 = (0..N).map(|val| (W2 * val as f32).sin()); 50 | let s = s1.zip(s2).map(|(ess1, ess2)| ess1 + ess2); 51 | 52 | let mut s_complex: heapless::Vec = 53 | s.map(|f| Complex32 { re: f, im: 0.0 }).collect(); 54 | 55 | // Complex impulse response of filter 56 | let mut df_complex: heapless::Vec = H 57 | .iter() 58 | .cloned() 59 | .map(|f| Complex32 { re: f, im: 0.0 }) 60 | .chain(core::iter::repeat(Complex32 { re: 0.0, im: 0.0 })) 61 | //fill rest with zeros up to N 62 | .take(N) 63 | .collect(); 64 | 65 | // SAFETY microfft now only accepts arrays instead of slices to avoid runtime errors 66 | // Thats not great for us. However we can cheat since our slice into an array because 67 | // "The layout of a slice [T] of length N is the same as that of a [T; N] array." 68 | // https://rust-lang.github.io/unsafe-code-guidelines/layout/arrays-and-slices.html 69 | // this goes away when something like heapless vec is in standard library 70 | // https://github.com/rust-lang/rfcs/pull/2990 71 | unsafe { 72 | let ptr = &mut *(df_complex.as_mut_ptr() as *mut [Complex32; N]); 73 | 74 | // Finding the FFT of the filter 75 | let _ = cfft(ptr); 76 | } 77 | 78 | let time: ClockDuration = dwt.measure(|| { 79 | // SAFETY same as above 80 | unsafe { 81 | let ptr = &mut *(s_complex.as_mut_ptr() as *mut [Complex32; N]); 82 | 83 | // Finding the FFT of the input signal 84 | let _ = cfft(ptr); 85 | } 86 | 87 | // Filtering in the frequency domain 88 | let y_complex = s_complex 89 | .iter() 90 | .zip(df_complex.iter()) 91 | //multiply complex 92 | .map(|(s, df)| Complex32 { 93 | re: s.re * df.re - s.im * df.im, 94 | im: s.re * df.im + s.im * df.re, 95 | }); 96 | 97 | // Finding the complex result in time domain 98 | // supposed to be inverse transform but microfft doesnt have it 99 | // Could patch it in. inverse DFT is the same as the DFT, but with the 100 | // opposite sign in the exponent and a 1/N factor, any FFT algorithm can 101 | // easily be adapted for it. 102 | // just dtfse approx instead for now 103 | let _y_freq: heapless::Vec = dtfse(y_complex.clone(), 15).collect(); 104 | }); 105 | rprintln!("dft ticks: {:?}", time.as_ticks()); 106 | 107 | // signal to probe-run to exit 108 | loop { 109 | cortex_m::asm::bkpt() 110 | } 111 | } 112 | 113 | static H: &[f32] = &[ 114 | 0.002044, 0.007806, 0.014554, 0.020018, 0.024374, 0.027780, 0.030370, 0.032264, 0.033568, 115 | 0.034372, 0.034757, 0.034791, 0.034534, 0.034040, 0.033353, 0.032511, 0.031549, 0.030496, 116 | 0.029375, 0.028207, 0.027010, 0.025800, 0.024587, 0.023383, 0.022195, 0.021031, 0.019896, 117 | 0.018795, 0.017730, 0.016703, 0.015718, 0.014774, 0.013872, 0.013013, 0.012196, 0.011420, 118 | 0.010684, 0.009989, 0.009331, 0.008711, 0.008127, 0.007577, 0.007061, 0.006575, 0.006120, 119 | 0.005693, 0.005294, 0.004920, 0.004570, 0.004244, 0.003939, 0.003655, 0.003389, 0.003142, 120 | 0.002912, 0.002698, 0.002499, 0.002313, 0.002141, 0.001981, 0.001833, 0.001695, 0.001567, 121 | 0.001448, 122 | ]; 123 | 124 | fn dtfse + Clone>( 125 | coeff: I, 126 | k_var: usize, 127 | ) -> impl Iterator { 128 | let size = N as f32; 129 | (0..N).map(move |n| { 130 | coeff 131 | .clone() 132 | .take(k_var + 1) 133 | .enumerate() 134 | .map(|(k, complex)| { 135 | let a = (complex.re * complex.re + complex.im * complex.im).sqrt(); 136 | let p = complex.im.atan2(complex.re); 137 | a * ((2.0 * PI * k as f32 * n as f32 / size) + p).cos() / size 138 | }) 139 | .sum::() 140 | }) 141 | } 142 | -------------------------------------------------------------------------------- /lab4/examples/4_1_dft_calculations.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining the DFT operation using standard math 2 | //! functions. Here, we have a digital input signal as the sum of two sinusoids 3 | //! with different frequencies. The complex form of this signal is represented 4 | //! with s_complex array, the frequency component of this signal is found by the 5 | //! DFT function. Real and imaginary parts of the obtained DFT are represented 6 | //! with XR and XI arrays. The magnitude of DFT is kept in the Mag array. 7 | //! 8 | //! Requires `cargo install probe-run` 9 | //! `cargo run --release --example 4_1_dft_calculations` 10 | 11 | #![no_std] 12 | #![no_main] 13 | 14 | use panic_break as _; 15 | use stm32f4xx_hal as hal; 16 | 17 | use core::f32::consts::PI; 18 | use hal::{dwt::ClockDuration, dwt::DwtExt, prelude::*, stm32}; 19 | use micromath::F32Ext; 20 | use rtt_target::{rprintln, rtt_init_print}; 21 | 22 | const N: usize = 256; 23 | 24 | const W1: f32 = core::f32::consts::PI / 128.0; 25 | const W2: f32 = core::f32::consts::PI / 4.0; 26 | // const W2: f32 = core::f32::consts::PI / 5.0; 27 | 28 | #[cortex_m_rt::entry] 29 | fn main() -> ! { 30 | rtt_init_print!(BlockIfFull, 128); 31 | 32 | let dp = stm32::Peripherals::take().unwrap(); 33 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 34 | 35 | // Set up the system clock. 36 | let rcc = dp.RCC.constrain(); 37 | 38 | let clocks = rcc 39 | .cfgr 40 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 41 | .sysclk(168.mhz()) 42 | .freeze(); 43 | 44 | // Create a delay abstraction based on DWT cycle counter 45 | let dwt = cp.DWT.constrain(cp.DCB, clocks); 46 | 47 | // Complex sum of sinusoidal signals 48 | let s1 = (0..N).map(|val| (W1 * val as f32).sin()); 49 | let s2 = (0..N).map(|val| (W2 * val as f32).sin()); 50 | let s = s1.zip(s2).map(|(ess1, ess2)| ess1 + ess2); 51 | 52 | // map it to real, leave im blank well fill in with dft 53 | let dtfsecoef = s.map(|f| Complex32 { re: f, im: 0.0 }); 54 | 55 | let time: ClockDuration = dwt.measure(|| { 56 | let dft = dft(dtfsecoef.clone()); 57 | 58 | //Magnitude calculation 59 | let _mag: heapless::Vec = dft 60 | .map(|complex| (complex.re * complex.re + complex.im * complex.im).sqrt()) 61 | .collect(); 62 | }); 63 | rprintln!("ticks: {:?}", time.as_ticks()); 64 | 65 | // signal to probe-run to exit 66 | loop { 67 | cortex_m::asm::bkpt() 68 | } 69 | } 70 | 71 | fn dft + Clone>(input: I) -> impl Iterator { 72 | let size = N as f32; 73 | (0..N).map(move |k| { 74 | input 75 | .clone() 76 | .enumerate() 77 | .fold((0f32, 0f32), |(mut sum_re, mut sum_im), (n, complex)| { 78 | let n = n as f32; 79 | sum_re += complex.re * (2.0 * PI * k as f32 * n / size).cos() 80 | + complex.im * (2.0 * PI * k as f32 * n / size).sin(); 81 | sum_im += -complex.im * (2.0 * PI * k as f32 * n / size).cos() 82 | + complex.re * (2.0 * PI * k as f32 * n / size).sin(); 83 | 84 | (sum_re, sum_im) 85 | }) 86 | .into() 87 | }) 88 | } 89 | 90 | struct Complex32 { 91 | re: f32, 92 | im: f32, 93 | } 94 | 95 | impl From<(f32, f32)> for Complex32 { 96 | fn from(incoming: (f32, f32)) -> Self { 97 | Complex32 { 98 | re: incoming.0, 99 | im: incoming.1, 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lab4/examples/4_3_dft_calculations.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining the DFT operation using standard math 2 | //! functions. Here, we have a digital input signal as the sum of two sinusoids 3 | //! with different frequencies. The complex form of this signal is represented 4 | //! with s_complex array, the frequency component of this signal is found by the 5 | //! DFT function. Real and imaginary parts of the obtained DFT are represented 6 | //! with XR and XI arrays. The magnitude of DFT is kept in the Mag array. 7 | //! 8 | //! Requires `cargo install probe-run` 9 | //! `cargo run --release --example 4_3_dft_calculations` 10 | //! 11 | //! I think its supposed to be faster with their functions, and their sin/cos is 12 | //! decently faster.. but something about bringing our sqrt across for them and 13 | //! our ability to optimize pure rust is making us faster actually! 14 | 15 | #![no_std] 16 | #![no_main] 17 | 18 | use panic_break as _; 19 | use stm32f4xx_hal as hal; 20 | 21 | use cmsis_dsp_sys::arm_cmplx_mag_f32; 22 | use core::f32::consts::PI; 23 | use cty::{c_float, uint32_t}; 24 | use hal::{dwt::ClockDuration, dwt::DwtExt, prelude::*, stm32}; 25 | use micromath::F32Ext; 26 | use rtt_target::{rprintln, rtt_init_print}; 27 | 28 | const N: usize = 256; 29 | 30 | const W1: f32 = core::f32::consts::PI / 128.0; 31 | const W2: f32 = core::f32::consts::PI / 4.0; 32 | // const W2: f32 = core::f32::consts::PI / 5.0; 33 | 34 | #[cortex_m_rt::entry] 35 | fn main() -> ! { 36 | rtt_init_print!(BlockIfFull, 128); 37 | 38 | let dp = stm32::Peripherals::take().unwrap(); 39 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 40 | 41 | // Set up the system clock. 42 | let rcc = dp.RCC.constrain(); 43 | 44 | let clocks = rcc 45 | .cfgr 46 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 47 | .sysclk(168.mhz()) 48 | .freeze(); 49 | 50 | // Create a delay abstraction based on DWT cycle counter 51 | let dwt = cp.DWT.constrain(cp.DCB, clocks); 52 | 53 | // Complex sum of sinusoidal signals 54 | let s1 = (0..N).map(|val| (W1 * val as f32).sin()); 55 | let s2 = (0..N).map(|val| (W2 * val as f32).sin()); 56 | let s = s1.zip(s2).map(|(ess1, ess2)| ess1 + ess2); 57 | 58 | // map it to real, leave im blank well fill in with dft 59 | let dtfsecoef: heapless::Vec = s.map(|f| Complex32 { re: f, im: 0.0 }).collect(); 60 | 61 | let mut mag = [0f32; N]; 62 | 63 | let time: ClockDuration = dwt.measure(|| unsafe { 64 | let dft: heapless::Vec = dft(dtfsecoef.into_iter()).collect(); 65 | 66 | // Magnitude calculation 67 | // a union of two f32 are just two f32 side by side in memory? so this 68 | // cast seems to be safe here and lets us keep using Complexf32 which is 69 | // far more ergonomic 70 | arm_cmplx_mag_f32( 71 | dft.as_ptr() as *const c_float, 72 | mag.as_mut_ptr(), 73 | N as uint32_t, 74 | ); 75 | }); 76 | rprintln!("ticks: {:?}", time.as_ticks()); 77 | 78 | // signal to probe-run to exit 79 | loop { 80 | cortex_m::asm::bkpt() 81 | } 82 | } 83 | 84 | fn dft + Clone>(input: I) -> impl Iterator { 85 | let size = N as f32; 86 | (0..N).map(move |k| { 87 | input 88 | .clone() 89 | .enumerate() 90 | .fold((0f32, 0f32), |(mut sum_re, mut sum_im), (n, complex)| { 91 | let n = n as f32; 92 | sum_re += complex.re * (2.0 * PI * k as f32 * n / size).cos() 93 | + complex.im * (2.0 * PI * k as f32 * n / size).sin(); 94 | sum_im += -complex.im * (2.0 * PI * k as f32 * n / size).cos() 95 | + complex.re * (2.0 * PI * k as f32 * n / size).sin(); 96 | 97 | (sum_re, sum_im) 98 | }) 99 | .into() 100 | }) 101 | } 102 | 103 | #[derive(Clone)] 104 | struct Complex32 { 105 | re: f32, 106 | im: f32, 107 | } 108 | 109 | impl From<(f32, f32)> for Complex32 { 110 | fn from(incoming: (f32, f32)) -> Self { 111 | Complex32 { 112 | re: incoming.0, 113 | im: incoming.1, 114 | } 115 | } 116 | } 117 | 118 | //C needs access to a sqrt fn, lets use micromath 119 | #[no_mangle] 120 | pub extern "C" fn sqrtf(x: f32) -> f32 { 121 | x.sqrt() 122 | } 123 | -------------------------------------------------------------------------------- /lab4/examples/4_5_fft_calculations.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining the FFT operation using ARM CMSIS-DSP 2 | //! library functions. Here we have a digital input signal, sum of two 3 | //! sinusoidal signals with different frequencies. The complex form of this 4 | //! signal is represented with s_complex array in main.c file. Frequency 5 | //! components of this signal are found with arm_cfft_f32 function. Output of 6 | //! this function is saved in the input array. The magnitude of the output 7 | //! signal is calculated with the arm_cmplx_mag_f32 function. The result is 8 | //! saved in the Mag array. 9 | //! 10 | //! Requires `cargo install probe-run` 11 | //! `cargo run --release --example 4_5_fft_calculations` 12 | 13 | #![no_std] 14 | #![no_main] 15 | 16 | use panic_break as _; 17 | use stm32f4xx_hal as hal; 18 | 19 | use cmsis_dsp_sys::{arm_cfft_f32, arm_cmplx_mag_f32}; 20 | use cty::uint32_t; 21 | use hal::{dwt::ClockDuration, dwt::DwtExt, prelude::*, stm32}; 22 | use itertools::Itertools; 23 | use micromath::F32Ext; 24 | use rtt_target::{rprintln, rtt_init_print}; 25 | 26 | use cmsis_dsp_sys::arm_cfft_sR_f32_len256 as arm_cfft_sR_f32; 27 | const N: usize = 256; 28 | const NCOMPLEX: usize = N * 2; 29 | 30 | const W1: f32 = core::f32::consts::PI / 128.0; 31 | const W2: f32 = core::f32::consts::PI / 4.0; 32 | // const W2: f32 = core::f32::consts::PI / 5.0; 33 | 34 | #[cortex_m_rt::entry] 35 | fn main() -> ! { 36 | rtt_init_print!(BlockIfFull, 128); 37 | 38 | let dp = stm32::Peripherals::take().unwrap(); 39 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 40 | 41 | // Set up the system clock. 42 | let rcc = dp.RCC.constrain(); 43 | 44 | let clocks = rcc 45 | .cfgr 46 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 47 | .sysclk(168.mhz()) 48 | .freeze(); 49 | 50 | // Create a delay abstraction based on DWT cycle counter 51 | let dwt = cp.DWT.constrain(cp.DCB, clocks); 52 | 53 | // Complex sum of sinusoidal signals 54 | let s1 = (0..N).map(|val| (W1 * val as f32).sin()); 55 | let s2 = (0..N).map(|val| (W2 * val as f32).sin()); 56 | 57 | //we wont use complex this time since, but just interleave the zeros for the imaginary part 58 | let mut s: heapless::Vec = s1 59 | .zip(s2) 60 | .map(|(ess1, ess2)| ess1 + ess2) 61 | .interleave_shortest(core::iter::repeat(0.0)) 62 | .collect(); 63 | 64 | let mut mag = [0f32; N]; 65 | 66 | let time: ClockDuration = dwt.measure(|| unsafe { 67 | //CFFT calculation 68 | arm_cfft_f32(&arm_cfft_sR_f32, s.as_mut_ptr(), 0, 1); 69 | 70 | // Magnitude calculation 71 | arm_cmplx_mag_f32(s.as_ptr(), mag.as_mut_ptr(), N as uint32_t); 72 | }); 73 | rprintln!("ticks: {:?}", time.as_ticks()); 74 | 75 | // signal to probe-run to exit 76 | loop { 77 | cortex_m::asm::bkpt() 78 | } 79 | } 80 | 81 | //C needs access to a sqrt fn, lets use micromath 82 | #[no_mangle] 83 | pub extern "C" fn sqrtf(x: f32) -> f32 { 84 | x.sqrt() 85 | } 86 | -------------------------------------------------------------------------------- /lab4/examples/4_5_fft_calculations_microfft.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining the FFT operation using ARM CMSIS-DSP 2 | //! library functions. Here we have a digital input signal, sum of two 3 | //! sinusoidal signals with different frequencies. The complex form of this 4 | //! signal is represented with s_complex array in main.c file. Frequency 5 | //! components of this signal are found with arm_cfft_f32 function. Output of 6 | //! this function is saved in the input array. The magnitude of the output 7 | //! signal is calculated with the arm_cmplx_mag_f32 function. The result is 8 | //! saved in the Mag array. 9 | //! 10 | //! Requires `cargo install probe-run` 11 | //! `cargo run --release --example 4_5_fft_calculations_microfft` 12 | 13 | #![no_std] 14 | #![no_main] 15 | 16 | use panic_break as _; 17 | use stm32f4xx_hal as hal; 18 | 19 | use hal::{dwt::ClockDuration, dwt::DwtExt, prelude::*, stm32}; 20 | use microfft::Complex32; 21 | use micromath::F32Ext; 22 | use rtt_target::{rprintln, rtt_init_print}; 23 | 24 | use microfft::complex::cfft_256 as cfft; 25 | const N: usize = 256; 26 | 27 | const W1: f32 = core::f32::consts::PI / 128.0; 28 | const W2: f32 = core::f32::consts::PI / 4.0; 29 | // const W2: f32 = core::f32::consts::PI / 5.0; 30 | 31 | #[cortex_m_rt::entry] 32 | fn main() -> ! { 33 | rtt_init_print!(BlockIfFull, 128); 34 | 35 | let dp = stm32::Peripherals::take().unwrap(); 36 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 37 | 38 | // Set up the system clock. 39 | let rcc = dp.RCC.constrain(); 40 | 41 | let clocks = rcc 42 | .cfgr 43 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 44 | .sysclk(168.mhz()) 45 | .freeze(); 46 | 47 | // Create a delay abstraction based on DWT cycle counter 48 | let dwt = cp.DWT.constrain(cp.DCB, clocks); 49 | 50 | // Complex sum of sinusoidal signals 51 | let s1 = (0..N).map(|val| (W1 * val as f32).sin()); 52 | let s2 = (0..N).map(|val| (W2 * val as f32).sin()); 53 | let s = s1.zip(s2).map(|(ess1, ess2)| ess1 + ess2); 54 | 55 | // map it to real, leave im blank well fill in with dft 56 | let mut dtfsecoef: heapless::Vec = 57 | s.map(|f| Complex32 { re: f, im: 0.0 }).collect(); 58 | 59 | let time: ClockDuration = dwt.measure(|| { 60 | // SAFETY microfft now only accepts arrays instead of slices to avoid runtime errors 61 | // Thats not great for us. However we can cheat since our slice into an array because 62 | // "The layout of a slice [T] of length N is the same as that of a [T; N] array." 63 | // https://rust-lang.github.io/unsafe-code-guidelines/layout/arrays-and-slices.html 64 | // this goes away when something like heapless vec is in standard library 65 | // https://github.com/rust-lang/rfcs/pull/2990 66 | unsafe { 67 | let ptr = &mut *(dtfsecoef.as_mut_ptr() as *mut [Complex32; N]); 68 | 69 | // Coefficient calculation with CFFT function 70 | // well use microfft uses an in place Radix-2 FFT 71 | // it re-returns our array in case we were going to chain calls, throw it away 72 | let _ = cfft(ptr); 73 | } 74 | 75 | // Magnitude calculation 76 | let _mag: heapless::Vec = dtfsecoef 77 | .iter() 78 | .map(|complex| (complex.re * complex.re + complex.im * complex.im).sqrt()) 79 | .collect(); 80 | }); 81 | rprintln!("ticks: {:?}", time.as_ticks()); 82 | 83 | // signal to probe-run to exit 84 | loop { 85 | cortex_m::asm::bkpt() 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lab4/examples/4_6_fft_accelerometer.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining the FFT operation on real-world signals. 2 | //! Here we first sample the acceleromater data. The sampling period is set as 3 | //! 10 milliseconds. The complex form of this signal is stored in X array. 4 | //! Frequency components of this signal are obtained with the arm_cfft_f32 5 | //! function. Output of this function is saved in the input array. Magnitude of 6 | //! the output signal is calculated with the arm_cmplx_mag_f32 function. The 7 | //! result is saved in the Mag array. 8 | //! 9 | //! Requires `cargo install probe-run` 10 | //! `cargo run --release --example 4_6_fft_accelerometer` 11 | 12 | #![no_std] 13 | #![no_main] 14 | 15 | use panic_break as _; 16 | use stm32f4xx_hal as hal; 17 | 18 | use cmsis_dsp_sys::{arm_cfft_f32, arm_cmplx_mag_f32}; 19 | use cty::uint32_t; 20 | use hal::{prelude::*, spi, stm32}; 21 | use itertools::Itertools; 22 | use lis3dsh::{accelerometer::RawAccelerometer, Lis3dsh}; 23 | use micromath::F32Ext; 24 | use rtt_target::{rprintln, rtt_init_print}; 25 | 26 | use cmsis_dsp_sys::arm_cfft_sR_f32_len512 as arm_cfft_sR_f32; 27 | const N: usize = 512; 28 | const NCOMPLEX: usize = N * 2; 29 | 30 | #[cortex_m_rt::entry] 31 | fn main() -> ! { 32 | rtt_init_print!(BlockIfFull, 128); 33 | 34 | let dp = stm32::Peripherals::take().unwrap(); 35 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 36 | 37 | // Set up the system clock. 38 | let rcc = dp.RCC.constrain(); 39 | 40 | let clocks = rcc 41 | .cfgr 42 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 43 | .sysclk(168.mhz()) 44 | .freeze(); 45 | 46 | let mut delay = hal::delay::Delay::new(cp.SYST, clocks); 47 | 48 | let gpioa = dp.GPIOA.split(); 49 | let gpioe = dp.GPIOE.split(); 50 | 51 | let sck = gpioa.pa5.into_alternate_af5().internal_pull_up(false); 52 | let miso = gpioa.pa6.into_alternate_af5().internal_pull_up(false); 53 | let mosi = gpioa.pa7.into_alternate_af5().internal_pull_up(false); 54 | 55 | let spi = spi::Spi::spi1( 56 | dp.SPI1, 57 | (sck, miso, mosi), 58 | spi::Mode { 59 | polarity: spi::Polarity::IdleLow, 60 | phase: spi::Phase::CaptureOnFirstTransition, 61 | }, 62 | 10.mhz().into(), 63 | clocks, 64 | ); 65 | 66 | let chip_select = gpioe.pe3.into_push_pull_output(); 67 | let mut lis3dsh = Lis3dsh::new_spi(spi, chip_select); 68 | lis3dsh.init(&mut delay).unwrap(); 69 | 70 | // dont love the idea of delaying in an iterator ... 71 | let dtfsecoef = (0..N).map(|_| { 72 | while !lis3dsh.is_data_ready().unwrap() {} 73 | let dat = lis3dsh.accel_raw().unwrap(); 74 | dat[0] as f32 75 | }); 76 | 77 | let mut dtfsecoef: heapless::Vec = dtfsecoef 78 | .interleave_shortest(core::iter::repeat(0.0)) 79 | .collect(); 80 | 81 | let mut mag = [0f32; N]; 82 | 83 | unsafe { 84 | //CFFT calculation 85 | arm_cfft_f32(&arm_cfft_sR_f32, dtfsecoef.as_mut_ptr(), 0, 1); 86 | 87 | // Magnitude calculation 88 | arm_cmplx_mag_f32(dtfsecoef.as_ptr(), mag.as_mut_ptr(), N as uint32_t); 89 | } 90 | 91 | rprintln!("mag: {:?}", mag); 92 | 93 | // signal to probe-run to exit 94 | loop { 95 | cortex_m::asm::bkpt() 96 | } 97 | } 98 | 99 | //C needs access to a sqrt fn, lets use micromath 100 | #[no_mangle] 101 | pub extern "C" fn sqrtf(x: f32) -> f32 { 102 | x.sqrt() 103 | } 104 | -------------------------------------------------------------------------------- /lab4/examples/4_6_fft_accelerometer_microfft.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining the FFT operation on real-world signals. 2 | //! Here we first sample the acceleromater data. The sampling period is set as 3 | //! 10 milliseconds. The complex form of this signal is stored in X array. 4 | //! Frequency components of this signal are obtained with the arm_cfft_f32 5 | //! function. Output of this function is saved in the input array. Magnitude of 6 | //! the output signal is calculated with the arm_cmplx_mag_f32 function. The 7 | //! result is saved in the Mag array. 8 | //! 9 | //! Requires `cargo install probe-run` 10 | //! `cargo run --release --example 4_6_fft_accelerometer_microfft` 11 | 12 | #![no_std] 13 | #![no_main] 14 | 15 | use panic_break as _; 16 | use stm32f4xx_hal as hal; 17 | 18 | use hal::{prelude::*, spi, stm32}; 19 | use lis3dsh::{accelerometer::RawAccelerometer, Lis3dsh}; 20 | use microfft::Complex32; 21 | use micromath::F32Ext; 22 | use rtt_target::{rprintln, rtt_init_print}; 23 | 24 | use microfft::complex::cfft_512 as cfft; 25 | const N: usize = 512; 26 | 27 | #[cortex_m_rt::entry] 28 | fn main() -> ! { 29 | rtt_init_print!(BlockIfFull, 128); 30 | 31 | let dp = stm32::Peripherals::take().unwrap(); 32 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 33 | 34 | // Set up the system clock. 35 | let rcc = dp.RCC.constrain(); 36 | 37 | let clocks = rcc 38 | .cfgr 39 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 40 | .sysclk(168.mhz()) 41 | .freeze(); 42 | 43 | let mut delay = hal::delay::Delay::new(cp.SYST, clocks); 44 | 45 | let gpioa = dp.GPIOA.split(); 46 | let gpioe = dp.GPIOE.split(); 47 | 48 | let sck = gpioa.pa5.into_alternate_af5().internal_pull_up(false); 49 | let miso = gpioa.pa6.into_alternate_af5().internal_pull_up(false); 50 | let mosi = gpioa.pa7.into_alternate_af5().internal_pull_up(false); 51 | 52 | let spi = spi::Spi::spi1( 53 | dp.SPI1, 54 | (sck, miso, mosi), 55 | spi::Mode { 56 | polarity: spi::Polarity::IdleLow, 57 | phase: spi::Phase::CaptureOnFirstTransition, 58 | }, 59 | 10.mhz().into(), 60 | clocks, 61 | ); 62 | 63 | let chip_select = gpioe.pe3.into_push_pull_output(); 64 | let mut lis3dsh = Lis3dsh::new_spi(spi, chip_select); 65 | lis3dsh.init(&mut delay).unwrap(); 66 | 67 | // dont love the idea of delaying in an iterator ... 68 | let mut dtfsecoef: heapless::Vec = (0..N) 69 | .map(|_| { 70 | while !lis3dsh.is_data_ready().unwrap() {} 71 | let dat = lis3dsh.accel_raw().unwrap(); 72 | 73 | Complex32 { 74 | re: dat[0] as f32, 75 | im: 0.0, 76 | } 77 | }) 78 | .collect(); 79 | 80 | // SAFETY microfft now only accepts arrays instead of slices to avoid runtime errors 81 | // Thats not great for us. However we can cheat since our slice into an array because 82 | // "The layout of a slice [T] of length N is the same as that of a [T; N] array." 83 | // https://rust-lang.github.io/unsafe-code-guidelines/layout/arrays-and-slices.html 84 | // this goes away when something like heapless vec is in standard library 85 | // https://github.com/rust-lang/rfcs/pull/2990 86 | unsafe { 87 | let ptr = &mut *(dtfsecoef.as_mut_ptr() as *mut [Complex32; N]); 88 | 89 | // Coefficient calculation with CFFT function 90 | // well use microfft uses an in place Radix-2 FFT 91 | // it re-returns our array in case we were going to chain calls, throw it away 92 | let _ = cfft(ptr); 93 | } 94 | 95 | // Magnitude calculation 96 | let mag: heapless::Vec = dtfsecoef 97 | .iter() 98 | .map(|complex| (complex.re * complex.re + complex.im * complex.im).sqrt()) 99 | .collect(); 100 | 101 | rprintln!("mag: {:?}", mag); 102 | 103 | // signal to probe-run to exit 104 | loop { 105 | cortex_m::asm::bkpt() 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lab4/examples/4_8_dtfse_calculations.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining the DTFSE operation. Here, we have a 2 | //! periodic square signal. The complex form of this signal is represented with 3 | //! s_complex array. DTFSE coefficients are calculated, then, the signal is 4 | //! approximated with the DTFSE function. This function returns its output in 5 | //! real form because original signal has only real parts in this example. The 6 | //! result is kept in the y_real array. 7 | //! 8 | //! Requires `cargo install probe-run` 9 | //! `cargo run --release --example 4_8_dtfse_calculations` 10 | 11 | #![no_std] 12 | #![no_main] 13 | 14 | use panic_break as _; 15 | use stm32f4xx_hal as hal; 16 | 17 | use cmsis_dsp_sys::{arm_cfft_f32, arm_cos_f32}; 18 | use core::f32::consts::PI; 19 | use cty::c_float; 20 | use hal::{dwt::ClockDuration, dwt::DwtExt, prelude::*, stm32}; 21 | use micromath::F32Ext; 22 | use rtt_target::{rprintln, rtt_init_print}; 23 | 24 | use cmsis_dsp_sys::arm_cfft_sR_f32_len16 as arm_cfft_sR_f32; 25 | const N: usize = 16; 26 | 27 | #[cortex_m_rt::entry] 28 | fn main() -> ! { 29 | rtt_init_print!(BlockIfFull, 128); 30 | 31 | let dp = stm32::Peripherals::take().unwrap(); 32 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 33 | 34 | // Set up the system clock. 35 | let rcc = dp.RCC.constrain(); 36 | 37 | let clocks = rcc 38 | .cfgr 39 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 40 | .sysclk(168.mhz()) 41 | .freeze(); 42 | 43 | // Create a delay abstraction based on DWT cycle counter 44 | let dwt = cp.DWT.constrain(cp.DCB, clocks); 45 | 46 | // square signal 47 | let square = (0..N).map(|n| if n < N / 2 { 1.0 } else { 0.0 }); 48 | 49 | // map it to real, leave im blank well fill in with cfft 50 | let mut dtfsecoef: heapless::Vec = 51 | square.map(|f| Complex32 { re: f, im: 0.0 }).collect(); 52 | 53 | //Coefficient calculation with CFFT function 54 | unsafe { 55 | arm_cfft_f32( 56 | &arm_cfft_sR_f32, 57 | dtfsecoef.as_mut_ptr() as *mut c_float, 58 | 0, 59 | 1, 60 | ); 61 | } 62 | 63 | let time: ClockDuration = dwt.measure(|| { 64 | let _y_real: heapless::Vec = dtfse(dtfsecoef.iter().cloned(), 15).collect(); 65 | }); 66 | rprintln!("ticks: {:?}", time.as_ticks()); 67 | 68 | // signal to probe-run to exit 69 | loop { 70 | cortex_m::asm::bkpt() 71 | } 72 | } 73 | 74 | fn dtfse + Clone>( 75 | coeff: I, 76 | k_var: usize, 77 | ) -> impl Iterator { 78 | let size = N as f32; 79 | (0..N).map(move |n| { 80 | coeff 81 | .clone() 82 | .take(k_var + 1) 83 | .enumerate() 84 | .map(|(k, complex)| { 85 | let a = (complex.re * complex.re + complex.im * complex.im).sqrt(); 86 | let p = complex.im.atan2(complex.re); 87 | unsafe { a * arm_cos_f32((2.0 * PI * k as f32 * n as f32 / size) + p) / size } 88 | }) 89 | .sum::() 90 | }) 91 | } 92 | 93 | #[derive(Clone)] 94 | struct Complex32 { 95 | re: f32, 96 | im: f32, 97 | } 98 | -------------------------------------------------------------------------------- /lab4/examples/4_8_dtfse_calculations_microfft.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for explaining the DTFSE operation. Here, we have a 2 | //! periodic square signal. The complex form of this signal is represented with 3 | //! s_complex array. DTFSE coefficients are calculated, then, the signal is 4 | //! approximated with the DTFSE function. This function returns its output in 5 | //! real form because original signal has only real parts in this example. The 6 | //! result is kept in the y_real array. 7 | //! 8 | //! Requires `cargo install probe-run` 9 | //! `cargo run --release --example 4_8_dtfse_calculations_microfft` 10 | 11 | #![no_std] 12 | #![no_main] 13 | 14 | use panic_break as _; 15 | use stm32f4xx_hal as hal; 16 | 17 | use core::f32::consts::PI; 18 | use hal::{dwt::ClockDuration, dwt::DwtExt, prelude::*, stm32}; 19 | use microfft::Complex32; 20 | use micromath::F32Ext; 21 | use rtt_target::{rprintln, rtt_init_print}; 22 | 23 | use microfft::complex::cfft_16 as cfft; 24 | const N: usize = 16; 25 | 26 | #[cortex_m_rt::entry] 27 | fn main() -> ! { 28 | rtt_init_print!(BlockIfFull, 128); 29 | 30 | let dp = stm32::Peripherals::take().unwrap(); 31 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 32 | 33 | // Set up the system clock. 34 | let rcc = dp.RCC.constrain(); 35 | 36 | let clocks = rcc 37 | .cfgr 38 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 39 | .sysclk(168.mhz()) 40 | .freeze(); 41 | 42 | // Create a delay abstraction based on DWT cycle counter 43 | let dwt = cp.DWT.constrain(cp.DCB, clocks); 44 | 45 | // square signal 46 | let square = (0..N).map(|n| if n < N / 2 { 1.0 } else { 0.0 }); 47 | 48 | // map it to real, leave im blank well fill in with cfft 49 | let mut dtfsecoef: heapless::Vec = 50 | square.map(|f| Complex32 { re: f, im: 0.0 }).collect(); 51 | 52 | // SAFETY microfft now only accepts arrays instead of slices to avoid runtime errors 53 | // Thats not great for us. However we can cheat since our slice into an array because 54 | // "The layout of a slice [T] of length N is the same as that of a [T; N] array." 55 | // https://rust-lang.github.io/unsafe-code-guidelines/layout/arrays-and-slices.html 56 | // this goes away when something like heapless vec is in standard library 57 | // https://github.com/rust-lang/rfcs/pull/2990 58 | unsafe { 59 | let ptr = &mut *(dtfsecoef.as_mut_ptr() as *mut [Complex32; N]); 60 | 61 | // Coefficient calculation with CFFT function 62 | // well use microfft uses an in place Radix-2 FFT 63 | // it re-returns our array in case we were going to chain calls, throw it away 64 | let _ = cfft(ptr); 65 | } 66 | 67 | let time: ClockDuration = dwt.measure(|| { 68 | let _y_real: heapless::Vec<_, N> = dtfse(dtfsecoef.iter().cloned(), 15).collect(); 69 | }); 70 | rprintln!("ticks: {:?}", time.as_ticks()); 71 | 72 | // signal to probe-run to exit 73 | loop { 74 | cortex_m::asm::bkpt() 75 | } 76 | } 77 | 78 | fn dtfse + Clone>( 79 | coeff: I, 80 | k_var: usize, 81 | ) -> impl Iterator { 82 | let size = N as f32; 83 | (0..N).map(move |n| { 84 | coeff 85 | .clone() 86 | .take(k_var + 1) 87 | .enumerate() 88 | .map(|(k, complex)| { 89 | let a = (complex.re * complex.re + complex.im * complex.im).sqrt(); 90 | let p = complex.im.atan2(complex.re); 91 | a * ((2.0 * PI * k as f32 * n as f32 / size) + p).cos() / size 92 | }) 93 | .sum::() 94 | }) 95 | } 96 | -------------------------------------------------------------------------------- /lab4/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE K = KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 1M 5 | RAM : ORIGIN = 0x20000000, LENGTH = 128K 6 | CCRAM : ORIGIN = 0x10000000, LENGTH = 64K 7 | } 8 | 9 | /* This is where the call stack will be allocated. */ 10 | /* The stack is of the full descending type. */ 11 | /* NOTE Do NOT modify `_stack_start` unless you know what you are doing */ 12 | _stack_start = ORIGIN(RAM) + LENGTH(RAM); 13 | -------------------------------------------------------------------------------- /lab4/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/stlink-v2.cfg] 2 | transport select hla_swd 3 | 4 | # increase working area to 64KB 5 | set WORKAREASIZE 0x10000 6 | 7 | source [find target/stm32f4x.cfg] 8 | 9 | reset_config srst_only 10 | -------------------------------------------------------------------------------- /lab4/openocd.gdb: -------------------------------------------------------------------------------- 1 | target extended-remote :3333 2 | 3 | # print demangled symbols 4 | set print asm-demangle on 5 | 6 | # set backtrace limit to not have infinite backtrace loops 7 | set backtrace limit 32 8 | 9 | # detect unhandled exceptions, hard faults and panics 10 | break DefaultHandler 11 | break HardFault 12 | break rust_begin_unwind 13 | 14 | # *try* to stop at the user entry point (it might be gone due to inlining) 15 | break main 16 | 17 | # send captured ITM to the file itm.txt 18 | # (the programmer's SWO pin on the STM32F4DISCOVERY is hard-wired to PB3. Make sure not to use it for a different purpose!) 19 | # 168000000 is the core clock frequency 20 | # monitor tpiu config internal itm.txt uart off 168000000 21 | 22 | 23 | # OR: make the microcontroller SWO (PB3) pin output compatible with UART (8N1) 24 | # 8000000 is the frequency of the SWO pin 25 | # monitor tpiu config external uart off 8000000 2000000 26 | 27 | # # enable ITM port 1 28 | # monitor itm port 1 on 29 | 30 | load 31 | 32 | # start the process but immediately halt the processor 33 | stepi 34 | -------------------------------------------------------------------------------- /lab5/.cargo/config: -------------------------------------------------------------------------------- 1 | # cargo run --example whatever 2 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 3 | 4 | # openocd 5 | #runner = "arm-none-eabi-gdb -x openocd.gdb" 6 | 7 | # probe-run 8 | runner = "probe-run --chip STM32F407VGTx" 9 | 10 | rustflags = [ 11 | "-C", "link-arg=-Tlink.x", 12 | ] 13 | 14 | [build] 15 | target = "thumbv7em-none-eabihf" 16 | -------------------------------------------------------------------------------- /lab5/.embed.toml: -------------------------------------------------------------------------------- 1 | [default.rtt] 2 | enabled = true 3 | 4 | [default.general] 5 | chip = "STM32F407VGTx" 6 | -------------------------------------------------------------------------------- /lab5/.vscode/.cortex-debug.peripherals.state.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /lab5/.vscode/.cortex-debug.registers.state.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /lab5/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": [ 6 | "rust-lang.rust", 7 | "marus25.cortex-debug", 8 | ], 9 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 10 | "unwantedRecommendations": [] 11 | } -------------------------------------------------------------------------------- /lab5/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "cortex-debug", 6 | "request": "launch", 7 | "servertype": "openocd", 8 | "cwd": "${workspaceRoot}", 9 | /* dont forget to select whichever example you are debugging*/ 10 | "executable": "./target/thumbv7em-none-eabihf/debug/examples/5_1_sampling_initial_setup", 11 | "preLaunchTask": "rust: cargo build examples", 12 | "name": "Debug (OpenOCD)", 13 | "device": "STM32F407VGT6", 14 | "configFiles": [ 15 | "board/stm32f4discovery.cfg" 16 | ], 17 | "runToMain": true, 18 | "gdbpath": "gdb-multiarch", 19 | "svdFile": "${workspaceRoot}/.vscode/STM32F407.svd" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /lab5/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.target": "thumbv7em-none-eabihf", 3 | "rust-analyzer.checkOnSave.allTargets": false, 4 | "rust-analyzer.checkOnSave.extraArgs": [ 5 | "--examples" 6 | ], 7 | "files.watcherExclude": { 8 | "**/.git/objects/**": true, 9 | "**/.git/subtree-cache/**": true, 10 | "**/target/**": true 11 | } 12 | } -------------------------------------------------------------------------------- /lab5/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "cargo", 6 | "command": "build", 7 | "args": [ 8 | "--examples", 9 | ], 10 | "problemMatcher": [ 11 | "$rustc" 12 | ], 13 | "group": "build", 14 | "label": "rust: cargo build examples" 15 | }, 16 | { 17 | "type": "cargo", 18 | "command": "build", 19 | "problemMatcher": [ 20 | "$rustc" 21 | ], 22 | "group": "build", 23 | "label": "rust: cargo build" 24 | }, 25 | { 26 | "type": "cargo", 27 | "command": "clean", 28 | "problemMatcher": [ 29 | "$rustc" 30 | ], 31 | "group": "build", 32 | "label": "rust: cargo clean" 33 | }, 34 | ] 35 | } -------------------------------------------------------------------------------- /lab5/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lab5" 3 | version = "0.1.0" 4 | authors = ["Jacob Rosenthal "] 5 | edition = "2018" 6 | resolver = "2" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | [dependencies] 10 | cortex-m = "0.7.2" 11 | cortex-m-rt = "0.6.12" 12 | panic_break = { path = "../panic_break" } 13 | panic-rtt-target = { version = "0.1.1", features = ["cortex-m"] } 14 | rtt-target = { version = "0.3.1", features = ["cortex-m"] } 15 | micromath = "1.0.1" 16 | heapless = { version = "0.7.0" } 17 | nb = "1.0.0" 18 | 19 | [dependencies.stm32f4xx-hal] 20 | features = ["stm32f407", "rt"] 21 | version = "0.9.0" 22 | 23 | [dependencies.embedded-hal] 24 | version = "0.2.5" 25 | 26 | # for cargo flash 27 | [package.metadata] 28 | chip = "STM32F407VGTx" 29 | 30 | [profile.dev] 31 | codegen-units = 1 32 | debug = 1 33 | debug-assertions = true # ! 34 | incremental = false 35 | lto = "fat" 36 | opt-level = 'z' # ! 37 | overflow-checks = false 38 | 39 | [profile.release] 40 | codegen-units = 1 41 | debug = 1 42 | debug-assertions = false 43 | incremental = false 44 | lto = "fat" 45 | opt-level = 3 46 | overflow-checks = false 47 | -------------------------------------------------------------------------------- /lab5/examples/5_1_sampling_initial_setup.rs: -------------------------------------------------------------------------------- 1 | //! The PA1 pin is used for the ADC channel. The ADC1 module is configured for 2 | //! 12 bit single conversion from Channel 1. The timer module triggers the ADC 3 | //! every 1/16000 sec. Also the ADC interrupt is enabled such that when the 4 | //! conversion ends, an interrupt is generated. 5 | //! 6 | //! Requires `cargo install probe-run` 7 | //! `cargo run --release --example 5_1_sampling_initial_setup` 8 | 9 | #![no_std] 10 | #![no_main] 11 | 12 | use panic_break as _; 13 | use stm32f4xx_hal as hal; 14 | 15 | use hal::adc::{config::AdcConfig, config::SampleTime, Adc}; 16 | use hal::{prelude::*, stm32}; 17 | use rtt_target::{rprintln, rtt_init_print}; 18 | 19 | const N: usize = 100; 20 | 21 | #[cortex_m_rt::entry] 22 | fn main() -> ! { 23 | rtt_init_print!(BlockIfFull, 128); 24 | 25 | let dp = stm32::Peripherals::take().unwrap(); 26 | let cp = cortex_m::peripheral::Peripherals::take().unwrap(); 27 | 28 | // Set up the system clock. 29 | let rcc = dp.RCC.constrain(); 30 | 31 | let clocks = rcc 32 | .cfgr 33 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 34 | .sysclk(168.mhz()) 35 | .freeze(); 36 | 37 | // let mut delay = Delay::new(cp.SYST, clocks); 38 | let mut delay = hal::delay::Delay::new(cp.SYST, clocks); 39 | 40 | let gpioa = dp.GPIOA.split(); 41 | let pa1 = gpioa.pa1.into_analog(); 42 | 43 | let mut adc = Adc::adc1(dp.ADC1, true, AdcConfig::default()); 44 | 45 | // doing blocking reads instead of interrupt driven 46 | let x: heapless::Vec = (0..N) 47 | .map(|_| { 48 | delay.delay_us(62u16); //0.0000625 s is 62.5us? 16.khz() 49 | adc.convert(&pa1, SampleTime::Cycles_84) 50 | }) 51 | .collect(); 52 | 53 | rprintln!("x: {:?}", x); 54 | 55 | // signal to probe-run to exit 56 | loop { 57 | cortex_m::asm::bkpt() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lab5/examples/5_3_analog_signal_generation.rs: -------------------------------------------------------------------------------- 1 | //! This project is used for generating analog signal from DAC output. 2 | //! Here, we first create two look-up table, sin_lookup and sq_lookup, for 3 | //! signal generation. Then, the signal is generated by triggering DAC in timer 4 | //! interrupt subroutine using these tables. Here, the signal waveform can be 5 | //! changed by pressing onboard push button of the STM32F4 Discovery kit. 6 | //! 7 | //! Requires `cargo install cargo-embed` 8 | //! `cargo embed --example 5_3_analog_signal_generation` 9 | 10 | #![no_std] 11 | #![no_main] 12 | 13 | use panic_rtt_target as _; 14 | use stm32f4xx_hal as hal; 15 | 16 | use core::cell::RefCell; 17 | use core::f32::consts::PI; 18 | use core::ops::DerefMut; 19 | use core::sync::atomic::{AtomicBool, Ordering}; 20 | use cortex_m::interrupt::{free, Mutex}; 21 | use hal::dac::{DacOut, DacPin}; 22 | use hal::gpio::{gpioa::PA0, Edge, ExtiPin, Input, PullDown}; 23 | use hal::timer::Timer; 24 | use hal::{interrupt, prelude::*, stm32}; 25 | use micromath::F32Ext; 26 | use nb::block; 27 | use rtt_target::rtt_init_print; 28 | 29 | const N: usize = 160; 30 | 31 | static BUTTON: Mutex>>>> = Mutex::new(RefCell::new(None)); 32 | static FLAG: AtomicBool = AtomicBool::new(true); 33 | 34 | #[cortex_m_rt::entry] 35 | fn main() -> ! { 36 | rtt_init_print!(BlockIfFull, 128); 37 | 38 | let mut dp = stm32::Peripherals::take().unwrap(); 39 | let _cp = cortex_m::peripheral::Peripherals::take().unwrap(); 40 | 41 | // Set up the system clock. 42 | let rcc = dp.RCC.constrain(); 43 | 44 | let clocks = rcc 45 | .cfgr 46 | .use_hse(8.mhz()) //discovery board has 8 MHz crystal for HSE 47 | .sysclk(168.mhz()) 48 | .freeze(); 49 | 50 | let gpioa = dp.GPIOA.split(); 51 | let mut syscfg = dp.SYSCFG.constrain(); 52 | 53 | let mut board_btn = gpioa.pa0.into_pull_down_input(); 54 | board_btn.make_interrupt_source(&mut syscfg); 55 | board_btn.enable_interrupt(&mut dp.EXTI); 56 | board_btn.trigger_on_edge(&mut dp.EXTI, Edge::FALLING); 57 | 58 | free(|cs| { 59 | BUTTON.borrow(cs).replace(Some(board_btn)); 60 | }); 61 | 62 | // Enable interrupts 63 | stm32::NVIC::unpend(hal::stm32::Interrupt::EXTI0); 64 | unsafe { 65 | stm32::NVIC::unmask(hal::stm32::Interrupt::EXTI0); 66 | }; 67 | 68 | let mut dac = dp.DAC.constrain(gpioa.pa4.into_analog()); 69 | 70 | dac.enable(); 71 | 72 | let sq_lookup: heapless::Vec = 73 | (0..N).map(|n| if n < N / 2 { 4095 } else { 0 }).collect(); 74 | 75 | // period 160 76 | let sin_lookup: heapless::Vec = (0..N) 77 | .map(|n| { 78 | let sindummy = (2.0 * PI * n as f32 / N as f32).sin(); 79 | ((sindummy * 2047.0) + 2048.0) as u16 80 | }) 81 | .collect(); 82 | 83 | // frequency dac 16khz, freq/period = 16000/160 = 100hz 84 | let mut timer = Timer::tim1(dp.TIM1, 16.khz(), clocks); 85 | // im not sure if you can create and start twice? 86 | block!(timer.wait()).unwrap(); 87 | 88 | loop { 89 | // little wiggly because not an interrupt.. 90 | let sin = FLAG.load(Ordering::Relaxed); 91 | if sin { 92 | for n in 0..N { 93 | dac.set_value(sin_lookup[n]); 94 | timer.start(16.khz()); 95 | block!(timer.wait()).unwrap(); 96 | } 97 | } else { 98 | for n in 0..N { 99 | dac.set_value(sq_lookup[n]); 100 | timer.start(16.khz()); 101 | block!(timer.wait()).unwrap(); 102 | } 103 | } 104 | } 105 | } 106 | 107 | // todo what orderings? 108 | // todo no cap, need debouncing 109 | #[interrupt] 110 | fn EXTI0() { 111 | free(|cs| { 112 | let mut btn_ref = BUTTON.borrow(cs).borrow_mut(); 113 | if let Some(ref mut btn) = btn_ref.deref_mut() { 114 | btn.clear_interrupt_pending_bit(); 115 | } 116 | let flag = FLAG.load(Ordering::Relaxed); 117 | FLAG.store(!flag, Ordering::Relaxed); 118 | }); 119 | } 120 | -------------------------------------------------------------------------------- /lab5/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE K = KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 1M 5 | RAM : ORIGIN = 0x20000000, LENGTH = 128K 6 | CCRAM : ORIGIN = 0x10000000, LENGTH = 64K 7 | } 8 | 9 | /* This is where the call stack will be allocated. */ 10 | /* The stack is of the full descending type. */ 11 | /* NOTE Do NOT modify `_stack_start` unless you know what you are doing */ 12 | _stack_start = ORIGIN(RAM) + LENGTH(RAM); 13 | -------------------------------------------------------------------------------- /lab5/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/stlink-v2.cfg] 2 | transport select hla_swd 3 | 4 | # increase working area to 64KB 5 | set WORKAREASIZE 0x10000 6 | 7 | source [find target/stm32f4x.cfg] 8 | 9 | reset_config srst_only 10 | -------------------------------------------------------------------------------- /lab5/openocd.gdb: -------------------------------------------------------------------------------- 1 | target extended-remote :3333 2 | 3 | # print demangled symbols 4 | set print asm-demangle on 5 | 6 | # set backtrace limit to not have infinite backtrace loops 7 | set backtrace limit 32 8 | 9 | # detect unhandled exceptions, hard faults and panics 10 | break DefaultHandler 11 | break HardFault 12 | break rust_begin_unwind 13 | 14 | # *try* to stop at the user entry point (it might be gone due to inlining) 15 | break main 16 | 17 | # send captured ITM to the file itm.txt 18 | # (the programmer's SWO pin on the STM32F4DISCOVERY is hard-wired to PB3. Make sure not to use it for a different purpose!) 19 | # 168000000 is the core clock frequency 20 | # monitor tpiu config internal itm.txt uart off 168000000 21 | 22 | 23 | # OR: make the microcontroller SWO (PB3) pin output compatible with UART (8N1) 24 | # 8000000 is the frequency of the SWO pin 25 | # monitor tpiu config external uart off 8000000 2000000 26 | 27 | # # enable ITM port 1 28 | # monitor itm port 1 on 29 | 30 | load 31 | 32 | # start the process but immediately halt the processor 33 | stepi 34 | -------------------------------------------------------------------------------- /panic_break/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "panic_break" 3 | version = "0.1.0" 4 | authors = ["Jacob Rosenthal "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | [dependencies] 9 | cortex-m = "0.7.2" 10 | rtt-target = { version = "0.3.1", features = ["cortex-m"] } 11 | -------------------------------------------------------------------------------- /panic_break/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use rtt_target::rprintln; 4 | 5 | // if an panic happens, print it out and signal probe-run to exit 6 | #[panic_handler] 7 | fn panic(info: &core::panic::PanicInfo) -> ! { 8 | rprintln!("{}", info); 9 | loop { 10 | cortex_m::asm::bkpt() // halt = exit probe-run 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Digital Signal Processing using Arm Cortex-M based Microcontrollers 2 | 3 | Translating the book from C to Rust. No relation to the author. 4 | 5 | ## Requires 6 | 7 | * Rust 1.5.1 8 | * `rustup target add thumbv7em-none-eabihf` 9 | * STM32F407G-DISC1 board 10 | * Possibly updated stlink firmware 11 | * (linux) udev rules /etc/udev/rules.d/49-stinkv2-1.rules and a reboot 12 | 13 | ```bash 14 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374a", \ 15 | MODE:="0666", \ 16 | SYMLINK+="stlinkv2-1_%n" 17 | 18 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", \ 19 | MODE:="0666", \ 20 | SYMLINK+="stlinkv2-1_%n" 21 | ``` 22 | 23 | ## GDB debugging 24 | 25 | Requires: 26 | 27 | * OpenOCD 0.11.0 28 | 29 | For desperate cases, swap your runner in .cargo/config for the openocd configuration, start an open ocd server with `openocd -f interface/stlink-v2-1.cfg -f target/stm32f4x.cfg`, and `cargo run`. 30 | --------------------------------------------------------------------------------