wip adc
This commit is contained in:
parent
5876e286c6
commit
8bc4f4f48d
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "firmware/stm32f0xx-hal"]
|
||||
path = firmware/stm32f0xx-hal
|
||||
url = https://github.com/stm32-rs/stm32f0xx-hal.git
|
||||
68
firmware/Cargo.lock
generated
68
firmware/Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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<u8, 64> = 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.
|
||||
241
firmware/src/adc.rs
Normal file
241
firmware/src/adc.rs
Normal file
@ -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::stm32::SPI2,
|
||||
hal::gpio::gpiob::PB10<Alternate<AF0>>,
|
||||
hal::gpio::gpiob::PB14<Alternate<AF0>>,
|
||||
hal::gpio::gpiob::PB15<Alternate<AF0>>,
|
||||
EightBit>,
|
||||
cs: hal::gpio::gpiob::PB12<Output<PushPull>>,
|
||||
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<hal::gpio::Input<hal::gpio::Floating>>,
|
||||
miso: hal::gpio::gpiob::PB14<hal::gpio::Input<hal::gpio::Floating>>,
|
||||
mosi: hal::gpio::gpiob::PB15<hal::gpio::Input<hal::gpio::Floating>>,
|
||||
cs: hal::gpio::gpiob::PB12<hal::gpio::Input<hal::gpio::Floating>>,
|
||||
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<u8, &'static str> {
|
||||
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<u8, &'static str> {
|
||||
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<u8>, Option<u8>) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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<hal::serial::Tx<stm32::USART1>>,
|
||||
pub midi_rx: MidiIn<hal::serial::Rx<stm32::USART1>>,
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -32,6 +32,8 @@ pub struct SevenSegmentDisplay {
|
||||
}
|
||||
|
||||
impl LedState {
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub fn boot_sequence(i: usize) -> Option<LedState> {
|
||||
let mut led_state = LedState::default();
|
||||
match i {
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
firmware/stm32f0xx-hal
Submodule
1
firmware/stm32f0xx-hal
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit c743505a730d7d7c36ec1dc29e7b54b162519c25
|
||||
Loading…
x
Reference in New Issue
Block a user