From 8bc4f4f48d577fe500768d70fa32c78027445f9f Mon Sep 17 00:00:00 2001 From: geens Date: Tue, 1 Jul 2025 11:02:20 +0200 Subject: [PATCH] wip adc --- .gitmodules | 3 + firmware/Cargo.lock | 68 +++--- firmware/Cargo.toml | 2 +- firmware/guide.md | 489 -------------------------------------- firmware/src/adc.rs | 241 +++++++++++++++++++ firmware/src/display.rs | 2 + firmware/src/hardware.rs | 10 + firmware/src/led_state.rs | 2 + firmware/src/main.rs | 16 ++ firmware/stm32f0xx-hal | 1 + 10 files changed, 312 insertions(+), 522 deletions(-) create mode 100644 .gitmodules delete mode 100644 firmware/guide.md create mode 100644 firmware/src/adc.rs create mode 160000 firmware/stm32f0xx-hal diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7b23ded --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "firmware/stm32f0xx-hal"] + path = firmware/stm32f0xx-hal + url = https://github.com/stm32-rs/stm32f0xx-hal.git diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index 3288ac2..90d5c97 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -8,7 +8,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" dependencies = [ - "rustc_version 0.2.3", + "rustc_version", ] [[package]] @@ -31,23 +31,21 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bxcan" -version = "0.6.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b13b4b2ea9ab2ba924063ebb86ad895cb79f4a79bf90f27949eb20c335b30f9" +checksum = "3110c36f496cf7110ab17c48ad714225330862101ec30197a9898006cd3e2862" dependencies = [ "bitflags", + "embedded-can", "nb 1.1.0", "vcell", ] [[package]] name = "cast" -version = "0.2.7" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" -dependencies = [ - "rustc_version 0.4.1", -] +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cortex-m" @@ -58,7 +56,7 @@ dependencies = [ "bare-metal 0.2.5", "bitfield", "critical-section", - "embedded-hal", + "embedded-hal 0.2.7", "volatile-register", ] @@ -130,6 +128,15 @@ dependencies = [ "defmt", ] +[[package]] +name = "embedded-can" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d2e857f87ac832df68fa498d18ddc679175cf3d2e4aa893988e5601baf9438" +dependencies = [ + "nb 1.1.0", +] + [[package]] name = "embedded-hal" version = "0.2.7" @@ -140,17 +147,29 @@ dependencies = [ "void", ] +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + [[package]] name = "embedded-midi" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26119337828c4c6a29d9c1661962eeec2a3f884b859d5713a70c6c4143988497" dependencies = [ - "embedded-hal", + "embedded-hal 0.2.7", "midi-types", "nb 1.1.0", ] +[[package]] +name = "embedded-storage" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" + [[package]] name = "midi-types" version = "0.1.7" @@ -224,16 +243,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver 0.9.0", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver 1.0.26", + "semver", ] [[package]] @@ -245,12 +255,6 @@ dependencies = [ "semver-parser", ] -[[package]] -name = "semver" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" - [[package]] name = "semver-parser" version = "0.7.0" @@ -259,9 +263,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "stm32f0" -version = "0.14.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad6efffc472f66098c4ec84d5907b4d001c550db86a7dfa607adf1ba94adbf82" +checksum = "0dce86e6516082d73a7b7f7d9d2ffd635b6aa95920203facedcadc339530c871" dependencies = [ "bare-metal 1.0.0", "cortex-m", @@ -278,7 +282,7 @@ dependencies = [ "critical-section", "defmt", "defmt-rtt", - "embedded-hal", + "embedded-hal 0.2.7", "embedded-midi", "midi-types", "nb 1.1.0", @@ -289,14 +293,14 @@ dependencies = [ [[package]] name = "stm32f0xx-hal" version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79544d457fe9a119c6fd02f0a16b8c20f39472b03199aac0a307d871a7015ad8" dependencies = [ "bare-metal 1.0.0", "bxcan", "cast", "cortex-m", - "embedded-hal", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-storage", "nb 1.1.0", "stm32f0", "void", diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index 18d6b3b..2a73a07 100755 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -21,7 +21,7 @@ embedded-midi = "0.1.2" midi-types = "0.1.7" nb = "1" panic-halt = "0.2" -stm32f0xx-hal = { version = "0.18", features = ["stm32f042", "rt"] } +stm32f0xx-hal = { path = "stm32f0xx-hal", features = ["stm32f042", "rt"] } # For debugging and logging defmt = "1" diff --git a/firmware/guide.md b/firmware/guide.md deleted file mode 100644 index 27f7d1b..0000000 --- a/firmware/guide.md +++ /dev/null @@ -1,489 +0,0 @@ -# STM32F042C4 Rust Development Guide - -Setting up Rust for STM32F042C4 microcontroller development has never been more streamlined, with modern tooling like **probe-rs** and mature HAL crates providing production-ready embedded development capabilities. This guide covers the complete workflow from environment setup through debugging with cheap ST-Link programmers, focusing on 2024-2025 best practices. - -The STM32F042C4 is a Cortex-M0-based microcontroller with 16KB Flash and 6KB RAM, requiring careful memory management but offering excellent USB and CAN capabilities. With probe-rs becoming the standard debugging tool and stm32f0xx-hal providing robust hardware abstraction, Rust embedded development now rivals traditional C/C++ toolchains while offering memory safety guarantees. - -## Rust development environment setup - -### Core toolchain installation - -The foundation requires Rust with ARM compilation targets and modern embedded tooling: - -```bash -# Install Rust via rustup -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -source ~/.cargo/env - -# Add ARM Cortex-M0 target for STM32F042C4 -rustup target add thumbv6m-none-eabi - -# Install essential binary utilities -rustup component add llvm-tools-preview -cargo install cargo-binutils - -# Install modern debugging and flashing tools -cargo install probe-rs --features cli -cargo install cargo-generate # For project templates -``` - -**probe-rs has emerged as the recommended tool** replacing OpenOCD for most STM32 Rust development. It provides native Rust integration, superior VS Code support, and built-in RTT logging without the complexity of GDB configuration. - -### Platform-specific dependencies - -**Linux systems** need additional development libraries: -```bash -# Ubuntu/Debian -sudo apt install libudev-dev pkg-config - -# Fedora -sudo dnf install libudev-devel pkgconf-pkg-config - -# Install udev rules for non-root probe access -sudo curl -L https://probe.rs/files/99-probe-rs.rules -o /etc/udev/rules.d/99-probe-rs.rules -sudo udevadm control --reload -``` - -**Windows systems** require Visual Studio Build Tools and will need WinUSB drivers for ST-Link clones using the Zadig tool. **macOS** typically works out of the box with Homebrew-installed dependencies. - -## STM32F042C4 specifications and HAL integration - -### Device characteristics - -The STM32F042C4T6 features an **ARM Cortex-M0 core running up to 48 MHz** with constrained but sufficient resources for many embedded applications: - -- **Flash Memory**: 16KB starting at 0x08000000 -- **SRAM**: 6KB starting at 0x20000000 -- **Key Peripherals**: USB 2.0 Full-speed, CAN bus, 2x USART, 2x SPI, I2C, 12-bit ADC -- **Architecture Target**: `thumbv6m-none-eabi` - -### Using stm32f0xx-hal crate - -The **stm32f0xx-hal crate** provides comprehensive hardware abstraction for the STM32F0 family, including specific support for the F042 variant: - -```toml -[dependencies] -cortex-m = "0.7" -cortex-m-rt = "0.7" -stm32f0xx-hal = { version = "0.18", features = ["stm32f042", "rt"] } -embedded-hal = "0.2" -panic-halt = "0.2" - -# Optional: Enable USB or CAN support -stm32-usbd = { version = "0.6", optional = true } -bxcan = { version = "0.8", optional = true } -``` - -The **"stm32f042" feature flag** ensures chip-specific peripheral configurations are available. The HAL implements standard embedded-hal traits for portability across different HAL implementations. - -## Project structure and configuration - -### Recommended project layout - -Modern STM32 Rust projects follow this structure: - -``` -stm32f042c4-project/ -├── Cargo.toml -├── build.rs -├── memory.x -├── .cargo/ -│ └── config.toml -├── src/ -│ └── main.rs -├── examples/ -│ └── blinky.rs -└── openocd.cfg (if using OpenOCD) -``` - -### Complete Cargo.toml configuration - -```toml -[package] -edition = "2021" -name = "stm32f042c4-blinky" -version = "0.1.0" - -[dependencies] -cortex-m = { version = "0.7", features = ["inline-asm"] } -cortex-m-rt = "0.7" -stm32f0xx-hal = { version = "0.18", features = ["stm32f042", "rt"] } -embedded-hal = "0.2" -panic-halt = "0.2" - -# For debugging and logging -defmt = "0.3" -defmt-rtt = "0.4" - -[profile.release] -debug = true # Keep debug info for better debugging -opt-level = "s" # Optimize for size (critical with 16KB Flash) -lto = true # Link-time optimization -codegen-units = 1 # Better optimization -panic = 'abort' # Reduce binary size -strip = false # Keep symbols for debugging -``` - -### Cargo configuration (.cargo/config.toml) - -```toml -[build] -target = "thumbv6m-none-eabi" - -[target.thumbv6m-none-eabi] -runner = "probe-rs run --chip STM32F042C4T6" -rustflags = [ - "-C", "link-arg=-Tlink.x", - "-C", "link-arg=--nmagic", -] - -[env] -DEFMT_LOG = "info" # Set logging level -``` - -### Build script (build.rs) - -```rust -use std::env; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; - -fn main() { - // Put memory.x in our output directory and ensure it's on linker search path - let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); - File::create(out.join("memory.x")) - .unwrap() - .write_all(include_bytes!("memory.x")) - .unwrap(); - println!("cargo:rustc-link-search={}", out.display()); - println!("cargo:rerun-if-changed=memory.x"); - println!("cargo:rustc-link-arg=--nmagic"); -} -``` - -## Memory.x configuration for STM32F042C4 - -The memory.x file defines the exact memory layout critical for proper linking: - -```linker-script -MEMORY -{ - /* STM32F042C4T6 specific memory layout */ - FLASH : ORIGIN = 0x08000000, LENGTH = 16K - RAM : ORIGIN = 0x20000000, LENGTH = 6K -} - -/* Stack grows downward from end of RAM */ -_stack_start = ORIGIN(RAM) + LENGTH(RAM); - -/* Custom sections for USB descriptors if needed */ -SECTIONS -{ - .usb_descriptors : - { - KEEP(*(.usb_descriptors.*)); - } > FLASH -} -``` - -**Critical memory constraints:** With only 16KB Flash and 6KB RAM, every byte matters. Always use release builds with size optimization (`opt-level = "s"`) and avoid dynamic allocation. - -## Basic blinking LED example - -Here's a complete blinking LED implementation optimized for STM32F042C4: - -```rust -#![no_std] -#![no_main] - -use panic_halt as _; -use cortex_m_rt::entry; -use stm32f0xx_hal::{ - prelude::*, - stm32, - delay::Delay, - gpio::*, -}; - -#[entry] -fn main() -> ! { - // Get device peripherals - let mut dp = stm32::Peripherals::take().unwrap(); - let cp = cortex_m::Peripherals::take().unwrap(); - - // Configure the clock to 48 MHz - let mut rcc = dp.RCC.configure() - .hsi48() - .enable_crs(dp.CRS) - .sysclk(48.mhz()) - .pclk(24.mhz()) - .freeze(&mut dp.FLASH); - - // Set up delay provider - let mut delay = Delay::new(cp.SYST, &rcc); - - // Configure GPIO - let gpioa = dp.GPIOA.split(&mut rcc); - - // Configure PA5 as push-pull output (common LED pin) - let mut led = gpioa.pa5.into_push_pull_output(&mut gpioa.moder, &mut gpioa.otyper); - - // Blink loop - loop { - led.set_high().ok(); - delay.delay_ms(500u16); - - led.set_low().ok(); - delay.delay_ms(500u16); - } -} -``` - -### Advanced LED example with RTT logging - -For debugging, add RTT logging capability: - -```rust -#![no_std] -#![no_main] - -use defmt::info; -use defmt_rtt as _; -use panic_halt as _; -use cortex_m_rt::entry; -use stm32f0xx_hal::{prelude::*, stm32, delay::Delay}; - -#[entry] -fn main() -> ! { - info!("Starting STM32F042C4 blinky with RTT logging"); - - let mut dp = stm32::Peripherals::take().unwrap(); - let cp = cortex_m::Peripherals::take().unwrap(); - - let mut rcc = dp.RCC.configure() - .hsi48() - .enable_crs(dp.CRS) - .sysclk(48.mhz()) - .freeze(&mut dp.FLASH); - - let mut delay = Delay::new(cp.SYST, &rcc); - let gpioa = dp.GPIOA.split(&mut rcc); - let mut led = gpioa.pa5.into_push_pull_output(&mut gpioa.moder, &mut gpioa.otyper); - - let mut counter = 0u32; - loop { - led.toggle().ok(); - info!("LED toggle #{}", counter); - counter += 1; - delay.delay_ms(1000u16); - } -} -``` - -## ST-Link clone setup and usage - -### Understanding ST-Link clones - -**ST-Link clones** are widely available Chinese copies of ST's official programming adapters, typically costing $2-5 versus $25+ for genuine hardware. However, they require careful setup and have reliability considerations. - -**Common clone issues include:** -- Inconsistent pinouts that don't match labeling -- Poor build quality causing intermittent connections -- Power delivery problems affecting target stability -- Firmware limitations blocking certain features - -### Essential wiring verification - -**Always verify pinout with a multimeter** before connecting. Standard connections should be: - -``` -ST-Link Clone STM32F042C4 -GND → GND -SWDIO → SWDIO (PA13) -SWCLK → SWCLK (PA14) -VAPP/VTref → 3.3V (CRITICAL - sets programming voltage) -VDD → 3.3V (if powering target from ST-Link) -``` - -**The VAPP connection is critical** - ST-Link determines programming voltage from this pin even when the target has external power. - -### Driver installation - -**Linux**: probe-rs works with standard udev rules: -```bash -sudo curl -L https://probe.rs/files/99-probe-rs.rules -o /etc/udev/rules.d/99-probe-rs.rules -sudo udevadm control --reload -``` - -**Windows**: Use Zadig to install WinUSB drivers for the ST-Link device. Download Zadig from https://zadig.akeo.ie/, select the ST-Link device, and install WinUSB driver. - -### Programming workflow with probe-rs - -**Basic commands:** -```bash -# List connected probes -probe-rs list - -# Get target information -probe-rs info --chip STM32F042C4T6 - -# Flash and run with RTT output -probe-rs run --chip STM32F042C4T6 target/thumbv6m-none-eabi/debug/blinky - -# Flash only without running -probe-rs download target/thumbv6m-none-eabi/release/blinky.elf --chip STM32F042C4T6 -``` - -**Cargo integration** enables `cargo run` to automatically flash and run: -```bash -# Build and flash in one command -cargo run --release - -# Build only -cargo build --release -``` - -## VS Code debugging and development workflow - -### Essential VS Code extensions - -1. **rust-analyzer** - Core Rust language support -2. **probe-rs** - Native debugging integration with DAP -3. **Cortex-Debug** - Alternative debugging option - -### VS Code launch configuration - -Create `.vscode/launch.json`: - -```json -{ - "version": "0.2.0", - "configurations": [ - { - "type": "probe-rs-debug", - "request": "launch", - "name": "Debug STM32F042C4", - "chip": "STM32F042C4T6", - "flashingConfig": { - "flashingEnabled": true, - "resetAfterFlashing": true, - "haltAfterReset": true - }, - "coreConfigs": [ - { - "coreIndex": 0, - "programBinary": "target/thumbv6m-none-eabi/debug/stm32f042c4-blinky", - "rttEnabled": true - } - ] - } - ] -} -``` - -This configuration enables **full debugging with breakpoints, variable inspection, and RTT logging** directly in VS Code. - -## Troubleshooting common issues - -### Memory and linking problems - -**"RAM overflowed" error** indicates stack or static variable overflow in the 6KB RAM constraint: -- Reduce stack-allocated arrays -- Use `static` variables instead of stack allocation -- Monitor memory usage with `cargo size` - -**"Load failed" during flashing** typically indicates incorrect memory.x values: -- Verify Flash size (16KB for STM32F042C4, not 32KB) -- Check SRAM size (6KB total) -- Ensure addresses match datasheet exactly - -### ST-Link connection issues - -**"No ST-Link detected" errors:** -1. Check USB cable quality and try different ports -2. Verify VAPP pin connection to target 3.3V -3. Ensure target has stable power supply -4. Try reducing programming speed: `--speed 1000` - -**Intermittent connection losses:** -- ST-Link clones often have poor power regulation -- Use external 3.3V supply for target instead of ST-Link power -- Add decoupling capacitors near the microcontroller - -### BOOT0 pin configuration - -**Program flashes but doesn't run** often indicates BOOT0 pin issues: -- BOOT0 must be LOW (connected to GND) for normal flash execution -- BOOT0 HIGH boots into system bootloader instead of user program -- Some development boards have BOOT0 jumpers requiring proper positioning - -### Debugging workflow optimization - -**RTT logging setup** for efficient debugging: -```rust -use defmt_rtt as _; -use defmt::{info, debug, error}; - -// Use throughout your code -info!("System initialized"); -debug!("Variable value: {}", variable); -error!("Critical error occurred"); -``` - -**Avoid WFI instruction** when using RTT, as it can interfere with real-time transfer communication. - -## Advanced considerations and best practices - -### Memory optimization strategies - -With only 16KB Flash, **aggressive optimization is essential:** - -```toml -[profile.release] -opt-level = "s" # Size optimization -lto = true # Link-time optimization -codegen-units = 1 # Better optimization -panic = 'abort' # Smaller panic handler -``` - -**Use `heapless` collections** instead of `std` alternatives: -```rust -use heapless::Vec; -use heapless::String; - -let mut buffer: Vec = Vec::new(); // Stack-allocated vector -let mut text: String<32> = String::new(); // Stack-allocated string -``` - -### Power management integration - -STM32F042C4 supports multiple low-power modes. **Basic sleep implementation:** - -```rust -use cortex_m::asm; -use stm32f0xx_hal::power::PowerMode; - -// Enter sleep mode -cortex_m::interrupt::free(|_| { - asm::wfi(); // Wait for interrupt -}); -``` - -### USB device development - -The STM32F042C4's **crystal-less USB capability** makes it ideal for USB projects: - -```rust -use stm32_usbd::UsbBus; -use usb_device::prelude::*; - -// USB setup requires specific clock configuration -let mut rcc = dp.RCC.configure() - .hsi48() - .enable_crs(dp.CRS) // Clock recovery system for crystal-less USB - .sysclk(48.mhz()) // Required for USB - .freeze(&mut dp.FLASH); -``` - -This comprehensive guide establishes a complete STM32F042C4 Rust development environment using modern 2024-2025 tooling. The combination of probe-rs for debugging, stm32f0xx-hal for hardware abstraction, and careful memory management provides a robust foundation for embedded development that rivals traditional C/C++ workflows while offering Rust's memory safety guarantees. \ No newline at end of file diff --git a/firmware/src/adc.rs b/firmware/src/adc.rs new file mode 100644 index 0000000..199e4db --- /dev/null +++ b/firmware/src/adc.rs @@ -0,0 +1,241 @@ +use stm32f0xx_hal as hal; +use hal::prelude::*; +use hal::spi::{Spi, Mode, Phase, Polarity, EightBit}; +use hal::gpio::{Output, PushPull, Alternate, AF0}; +use embedded_hal::blocking::spi::Transfer; +use defmt::*; + +pub const PEDAL_A_MIN: u8 = 10; +pub const PEDAL_A_MAX: u8 = 245; +pub const PEDAL_B_MIN: u8 = 10; +pub const PEDAL_B_MAX: u8 = 245; + +const IIR_ALPHA: u16 = 51; + +pub struct Tlc0832 { + spi: Spi>, + hal::gpio::gpiob::PB14>, + hal::gpio::gpiob::PB15>, + EightBit>, + cs: hal::gpio::gpiob::PB12>, + pedal_a_filtered: u16, + pedal_b_filtered: u16, + last_midi_a: u8, + last_midi_b: u8, +} + +impl Tlc0832 { + pub fn new( + spi2: hal::stm32::SPI2, + sck: hal::gpio::gpiob::PB10>, + miso: hal::gpio::gpiob::PB14>, + mosi: hal::gpio::gpiob::PB15>, + cs: hal::gpio::gpiob::PB12>, + rcc: &mut hal::rcc::Rcc, + ) -> Self { + cortex_m::interrupt::free(|critical_section| { + let sck = sck.into_alternate_af0(critical_section); + let miso = miso.into_alternate_af0(critical_section); + let mosi = mosi.into_alternate_af0(critical_section); + let cs = cs.into_push_pull_output(critical_section); + + let spi_mode = Mode { + polarity: Polarity::IdleLow, + phase: Phase::CaptureOnFirstTransition, + }; + + let spi = Spi::spi2( + spi2, + (sck, miso, mosi), + spi_mode, + 100.khz(), + rcc, + ); + + let adc = Self { + spi, + cs, + pedal_a_filtered: 128 << 8, + pedal_b_filtered: 128 << 8, + last_midi_a: 64, + last_midi_b: 64, + }; + + // Small delay to ensure SPI peripheral is ready + for _ in 0..1000 { + cortex_m::asm::nop(); + } + + adc + }) + } + + fn test_spi_loopback(&mut self, channel: u8) -> Result { + info!("SPI Loopback Test - Channel {}", channel); + + // Test pattern: send different values and check if we get them back + let test_values = [0x55, 0xAA, 0x12, 0x34]; + let test_byte = test_values[channel as usize % test_values.len()]; + + unsafe { + let spi2_base = 0x4000_3800 as *mut u32; + let dr_offset = 0x0C / 4; // Data register offset + let sr_offset = 0x08 / 4; // Status register offset + + let dr_ptr = spi2_base.add(dr_offset); + let sr_ptr = spi2_base.add(sr_offset); + + info!("Sending test byte: 0x{:02x}", test_byte); + + // Send test byte + dr_ptr.write_volatile(test_byte as u32); + + // Wait for TXE (transmit buffer empty) with timeout + let mut timeout = 1000; + while (sr_ptr.read_volatile() & (1 << 1)) == 0 { + timeout -= 1; + if timeout == 0 { + error!("Loopback timeout waiting for TXE"); + return Err("SPI loopback timeout"); + } + } + + // Wait for RXNE (receive buffer not empty) + timeout = 1000; + while (sr_ptr.read_volatile() & (1 << 0)) == 0 { + timeout -= 1; + if timeout == 0 { + error!("Loopback timeout waiting for RXNE"); + return Err("SPI loopback timeout"); + } + } + + let received = dr_ptr.read_volatile() as u8; + info!("Received: 0x{:02x}, Expected: 0x{:02x}", received, test_byte); + + if received == test_byte { + info!("✓ SPI Loopback SUCCESS!"); + Ok(received) + } else { + error!("✗ SPI Loopback FAILED - mismatch"); + Err("SPI loopback mismatch") + } + } + } + + pub fn read_channel(&mut self, channel: u8) -> Result { + if channel > 1 { + return Err("Invalid channel"); + } + + self.cs.set_high().ok(); + // Small delay before starting transaction + for _ in 0..100 { + cortex_m::asm::nop(); + } + + self.cs.set_low().ok(); + // Small delay after CS low + for _ in 0..100 { + cortex_m::asm::nop(); + } + + let start_bit = 1u8 << 7; + let channel_bit = if channel == 0 { 0x00 } else { 1u8 << 6 }; + let command = start_bit | channel_bit; + + info!("Reading channel {} with command 0x{:02x}", channel, command); + + // Use HAL SPI transfer - much simpler and more reliable + let mut data = [command, 0x00, 0x00]; // TLC0832 needs 3 bytes + let result = match self.spi.transfer(&mut data) { + Ok(response) => { + info!("SPI transfer successful: [{:02x}, {:02x}, {:02x}]", + response[0], response[1], response[2]); + // TLC0832 returns data in the 3rd byte for 8-bit mode + response[2] + }, + Err(_) => { + self.cs.set_high().ok(); + error!("SPI transfer error"); + return Err("SPI transfer error"); + } + }; + + // Small delay before CS high + for _ in 0..100 { + cortex_m::asm::nop(); + } + self.cs.set_high().ok(); + info!("Read channel {} = {}", channel, result); + Ok(result) + } + + fn apply_iir_filter(&mut self, channel: u8, new_value: u8) { + let new_value_scaled = (new_value as u16) << 8; + + match channel { + 0 => { + self.pedal_a_filtered = self.pedal_a_filtered - (self.pedal_a_filtered >> 8) * IIR_ALPHA / 256 + new_value_scaled * IIR_ALPHA / 256; + }, + 1 => { + self.pedal_b_filtered = self.pedal_b_filtered - (self.pedal_b_filtered >> 8) * IIR_ALPHA / 256 + new_value_scaled * IIR_ALPHA / 256; + }, + _ => {} + } + } + + fn adc_to_midi(&self, adc_value: u8, min_adc: u8, max_adc: u8) -> u8 { + if adc_value <= min_adc { + return 0; + } + if adc_value >= max_adc { + return 127; + } + + let range = max_adc - min_adc; + let scaled = ((adc_value - min_adc) as u16 * 127) / range as u16; + scaled.min(127) as u8 + } + + pub fn update_and_get_midi_changes(&mut self) -> (Option, Option) { + info!("Updating and getting MIDI changes"); + let mut midi_a_change = None; + let mut midi_b_change = None; + + match self.read_channel(0) { + Ok(raw_a) => { + self.apply_iir_filter(0, raw_a); + let filtered_a = (self.pedal_a_filtered >> 8) as u8; + let midi_a = self.adc_to_midi(filtered_a, PEDAL_A_MIN, PEDAL_A_MAX); + + if midi_a != self.last_midi_a { + self.last_midi_a = midi_a; + midi_a_change = Some(midi_a); + } + } + Err(e) => { + error!("Error reading channel 0: {}", e); + } + } + + match self.read_channel(1) { + Ok(raw_b) => { + self.apply_iir_filter(1, raw_b); + let filtered_b = (self.pedal_b_filtered >> 8) as u8; + let midi_b = self.adc_to_midi(filtered_b, PEDAL_B_MIN, PEDAL_B_MAX); + + if midi_b != self.last_midi_b { + self.last_midi_b = midi_b; + midi_b_change = Some(midi_b); + } + } + Err(e) => { + error!("Error reading channel 1: {}", e); + } + } + + (midi_a_change, midi_b_change) + } +} \ No newline at end of file diff --git a/firmware/src/display.rs b/firmware/src/display.rs index 1b7c18e..60218bb 100644 --- a/firmware/src/display.rs +++ b/firmware/src/display.rs @@ -4,6 +4,8 @@ use stm32f0xx_hal::{delay::Delay, prelude::*}; pub struct DisplayController; impl DisplayController { + + #[cfg(not(debug_assertions))] pub fn run_boot_sequence(bus: &mut Bus, delay: &mut Delay) { let mut boot_sequence_index = 0; while let Some(led_state) = LedState::boot_sequence(boot_sequence_index) { diff --git a/firmware/src/hardware.rs b/firmware/src/hardware.rs index 4c46785..500dffc 100644 --- a/firmware/src/hardware.rs +++ b/firmware/src/hardware.rs @@ -4,6 +4,7 @@ use hal::{delay::Delay, serial::Serial, stm32}; use hal::gpio::{Input, Floating, gpiob}; use embedded_midi::{MidiOut, MidiIn}; use crate::bus::Bus; +use crate::adc::Tlc0832; pub struct Hardware { pub delay: Delay, @@ -12,6 +13,7 @@ pub struct Hardware { pub bus: Bus, pub midi_tx: MidiOut>, pub midi_rx: MidiIn>, + pub adc: Tlc0832, } impl Hardware { @@ -52,6 +54,13 @@ impl Hardware { let midi_tx = MidiOut::new(tx); let midi_rx = MidiIn::new(rx); + let adc_sck = cortex_m::interrupt::free(|cs| gpiob.pb10.into_floating_input(cs)); + let adc_miso = cortex_m::interrupt::free(|cs| gpiob.pb14.into_floating_input(cs)); + let adc_mosi = cortex_m::interrupt::free(|cs| gpiob.pb15.into_floating_input(cs)); + let adc_cs = cortex_m::interrupt::free(|cs| gpiob.pb12.into_floating_input(cs)); + + let adc = Tlc0832::new(dp.SPI2, adc_sck, adc_miso, adc_mosi, adc_cs, &mut rcc); + Self { delay, button_1_5, @@ -59,6 +68,7 @@ impl Hardware { bus, midi_tx, midi_rx, + adc, } } } \ No newline at end of file diff --git a/firmware/src/led_state.rs b/firmware/src/led_state.rs index acb226a..9651c0a 100644 --- a/firmware/src/led_state.rs +++ b/firmware/src/led_state.rs @@ -32,6 +32,8 @@ pub struct SevenSegmentDisplay { } impl LedState { + + #[cfg(not(debug_assertions))] pub fn boot_sequence(i: usize) -> Option { let mut led_state = LedState::default(); match i { diff --git a/firmware/src/main.rs b/firmware/src/main.rs index ba0fb94..674edaa 100755 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -7,6 +7,7 @@ mod button; mod midi; mod hardware; mod display; +mod adc; use cortex_m_rt::entry; use panic_halt as _; @@ -17,6 +18,8 @@ use button::ButtonHandler; use midi::MidiProcessor; use hardware::Hardware; use display::DisplayController; +use embedded_midi::MidiMessage; +use midi_types::{Control, Channel, Value7}; defmt::timestamp!("{=u32}", { static mut COUNTER: u32 = 0; @@ -30,6 +33,7 @@ defmt::timestamp!("{=u32}", { fn main() -> ! { let mut hardware = Hardware::init(); + #[cfg(not(debug_assertions))] DisplayController::run_boot_sequence(&mut hardware.bus, &mut hardware.delay); let mut button_handler = ButtonHandler::new(); @@ -49,6 +53,18 @@ fn main() -> ! { } MidiProcessor::process_message(&mut hardware.midi_rx, &mut led_state); + + let (pedal_a_change, pedal_b_change) = hardware.adc.update_and_get_midi_changes(); + + if let Some(value) = pedal_a_change { + let msg = MidiMessage::ControlChange(Channel::C1, Control::from(1), Value7::new(value)); + hardware.midi_tx.write(&msg).ok(); + } + + if let Some(value) = pedal_b_change { + let msg = MidiMessage::ControlChange(Channel::C1, Control::from(7), Value7::new(value)); + hardware.midi_tx.write(&msg).ok(); + } } } } diff --git a/firmware/stm32f0xx-hal b/firmware/stm32f0xx-hal new file mode 160000 index 0000000..c743505 --- /dev/null +++ b/firmware/stm32f0xx-hal @@ -0,0 +1 @@ +Subproject commit c743505a730d7d7c36ec1dc29e7b54b162519c25