diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/architecture_diagram.svg b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/architecture_diagram.svg new file mode 100644 index 0000000000..56b892bd23 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/architecture_diagram.svg @@ -0,0 +1,3 @@ + + +
PWM
PWM
I2C

I2C
STM32U545
Embassy-RS firmware











STM32U545...
button_task
button_task
adc_task
adc_task
mic_task/BPM
mic_task/BPM
display_task
display_task
wifi task (JSON-> UART->AT)
wifi task (JSON-> UART->AT)
13xbuttons
piano octave
13xbuttons...
ADC
ADC
Potentiometers
volume+octave
Potentiometers...
ADC
ADC
MAX4466 mic
BPM detection
MAX4466 mic...
OLED SSD1306
note, chord, BPM
OLED SSD1306...
RGB LED
chord colour + BPM
RGB LED...
ESP01
WiFi Module
ESP01...
Browser Dashboard
Web audio+ BPM indicator
Browser Dashboard...
GPIO
GPIO
UART
UART
WebSocket
WebSocket
\ No newline at end of file diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/index.md b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/index.md new file mode 100644 index 0000000000..30baa92687 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/index.md @@ -0,0 +1,116 @@ +# MIDI Controller + +A physical MIDI controller with piano-layout keys, real-time chord detection, and BPM sensing over WiFi. + +:::info + +**Author**: Racu Delia-Andreea \ +**GitHub Project Link**: https://github.com/UPB-PMRust-Students/fils-project-2026-DeliaRacu + +::: + +## Description + +A physical MIDI controller with 13 piano-layout buttons, two potentiometers, an OLED display, an RGB LED, and a microphone. It connects via ESP-01 to a browser dashboard over WebSocket, where sound is synthesised using the Web Audio API. Firmware is written in Rust with Embassy-RS on an STM32U545. +Two modes: single note (one button + octave selector) and chord (3+ buttons, auto-detects Major/Minor/Diminished/Augmented). The microphone detects rhythmic input for real-time BPM detection, shown on the OLED and in the browser; the RGB LED pulses in sync with the beat, its colour set by the active chord type. + +## Motivation + +Studying both IoT at Politehnica and music at the Conservatory, I wanted a portable device to sketch chord progressions and test melodies without a full piano -- no DAW, no drivers, no cables. The BPM detection lets me clap a rhythm and see the tempo lock in instantly, just like a real musician would. Technically, the project spans async Rust firmware, ADC signal processing, I2C, UART, and WebSocket streaming across the full IoT stack. + +## Architecture + +The system is built around five concurrent Embassy-RS tasks running on the STM32U545 microcontroller: + +- **button_task** -- scans 13 GPIO pins every 10 ms, maintains pressed-key state, triggers chord detection +- **adc_task** -- reads both potentiometers, maps ADC values to volume (0-100) and octave (2-6) +- **mic_task** -- samples MAX4466 via ADC, detects amplitude peaks, computes rolling BPM, drives RGB LED via PWM +- **display_task** -- runs chord detection on state change, updates OLED over I2C +- **wifi_task** -- serialises state as JSON, sends to ESP-01 over UART using AT commands + +The ESP-01 connects to the local WiFi network and exposes a WebSocket server. The browser dashboard connects to it and uses the Web Audio API to synthesise sound in real time. + +![Architecture Diagram](architecture_diagram.svg) + +## Log + +### Week 5 - 11 May + +- Finalised the MIDI controller concept and main functionality. +- Defined the hardware architecture and main firmware components. + +### Week 12 - 18 May + +- Finalised the component selection and STM32U545 pin mapping. +- Designed the initial breadboard connections for the OLED, buttons, potentiometers, RGB LED, and microphone. +- Started setting up the Rust/Embassy development environment. + + +### Week 19 - 25 May + +- Started the OLED integration using the SSD1306 over I2C. +- Confirmed I2C1 mapping: PB6 (SCL) and PB7 (SDA). +- Created the initial OLED test firmware. +- Started debugging the Rust toolchain and Embassy configuration. + +![Work Progress](workprogress.webp.webp) + + +## Hardware + +The main microcontroller is the **STM32U545** on a NUCLEO-U545RE-Q development board, chosen for its multiple ADC channels, hardware I2C, hardware UART, and native Embassy-RS support. WiFi connectivity is handled by an **ESP-01 (ESP8266)** module communicating via UART AT commands. Thirteen **tactile push buttons** are arranged in the physical layout of a piano octave, each with a 10 kOhm pull-down resistor and software debounce. Two **10 kOhm potentiometers** control master volume and octave selection via ADC. An **SSD1306 OLED display** (128x64 px, I2C) shows the note or chord name, octave, WiFi status, and current BPM. An **RGB LED** (5mm, common-cathode) provides visual feedback: colour encodes the chord type and brightness pulses with the detected BPM. The **MAX4466 microphone module** provides an analog output (0-3.3V) read by ADC for peak detection and BPM computation. + +### Schematics +![KiCad Schematic](midi_controller.kicad.svg) + + +**Pin mapping:** + +| Signal | MCU Pin | Notes | +| --- | --- | --- | +| Buttons 0-12 | PC0-PC12 | 10 kOhm pull-down, active HIGH | +| Volume pot | PA0 (ADC1_IN5) | 0-3.3V | +| Octave pot | PA1 (ADC1_IN6) | 0-3.3V | +| Microphone | PA2 (ADC1_IN7) | MAX4466 output | +| OLED SDA | PB7 (I2C1_SDA) | 4.7 kOhm pull-up | +| OLED SCL | PB6 (I2C1_SCL) | 4.7 kOhm pull-up | +| RGB LED R | PB0 (TIM3_CH3) | 220 Ohm series resistor | +| RGB LED G | PB1 (TIM3_CH4) | 220 Ohm series resistor | +| RGB LED B | PA6 (TIM3_CH1) | 220 Ohm series resistor | +| ESP-01 TX | PA9 (USART1_TX) | 3.3V logic | +| ESP-01 RX | PA10 (USART1_RX) | 3.3V logic | + +### Bill of Materials + +| Device | Usage | Price | +| --- | --- | --- | +| [NUCLEO-U545RE-Q](https://www.st.com/en/evaluation-tools/nucleo-u545re-q.html) | Main microcontroller (STM32U545) | ~120 RON | +| [ESP-01 (ESP8266) + adapter](https://www.optimusdigital.ro/en/wireless-esp8266/13244-esp-01-esp8266-wifi-module.html) | WiFi module, UART AT commands | ~15 RON | +| [Tactile push buttons x13](https://www.optimusdigital.ro/en/buttons-and-switches/1119-6x6x6-push-button.html) | Piano octave keys | ~5 RON | +| [Potentiometer 10 kOhm x2](https://www.optimusdigital.ro/en/potentiometers/901-10k-wh148-linear-potentiometer.html) | Volume + octave selection | ~5 RON | +| [OLED SSD1306 128x64 I2C](https://www.optimusdigital.ro/en/lcds/194-096-i2c-oled-display.html) | Displays note, chord, BPM | ~20 RON | +| [RGB LED 5mm common-cathode](https://www.optimusdigital.ro/en/leds/483-rgb-led-5mm-common-cathode.html) | Chord colour + BPM pulse | ~1 RON | +| [MAX4466 microphone module](https://www.optimusdigital.ro/en/microphones/1328-max4466-microphone-amplifier.html) | BPM detection via ADC | ~25 RON | +| Resistors (10 kOhm x13, 220 Ohm x3, 4.7 kOhm x2) | Pull-downs, current limiting, I2C pull-ups | ~3 RON | +| Breadboard + jumper wires | Prototyping | ~15 RON | + +## Software + +| Library | Description | Usage | +| --- | --- | --- | +| [embassy-stm32](https://github.com/embassy-rs/embassy) | Async HAL for STM32U545 | GPIO, ADC, I2C, UART, PWM drivers | +| [embassy-executor](https://github.com/embassy-rs/embassy) | Async task scheduler | Runs all concurrent firmware tasks | +| [embassy-sync](https://github.com/embassy-rs/embassy) | Synchronisation primitives | Mutex and Signal for shared state between tasks | +| [embassy-time](https://github.com/embassy-rs/embassy) | Timestamp utilities | Inter-beat interval measurement for BPM detection | +| [ssd1306](https://github.com/jamwaffles/ssd1306) | OLED display driver over I2C | Renders note, chord name and BPM on the display | +| [embedded-graphics](https://github.com/embedded-graphics/embedded-graphics) | 2D graphics library | Text layout and rendering on the OLED | +| [heapless](https://github.com/rust-embedded/heapless) | Stack-allocated collections | Ring buffer for BPM interval history | +| [serde-json-core](https://github.com/rust-embedded-community/serde-json-core) | JSON serialisation for no_std | Serialises musical events into WebSocket messages | + +## Links + +1. [Embassy-RS framework](https://embassy.dev) +2. [STM32U545 reference manual](https://www.st.com/en/microcontrollers-microprocessors/stm32u545re.html) +3. [Web Audio API (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) +4. [MAX4466 datasheet](https://www.analog.com/en/products/max4466.html) +5. [SSD1306 datasheet](https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf) diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/.cargo/config.toml b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/.cargo/config.toml new file mode 100644 index 0000000000..37a8a4377a --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32U5G9ZJTxQ with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32U545RETx" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "info,embassy_stm32=warn" diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/Cargo.toml b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/Cargo.toml new file mode 100644 index 0000000000..bdc51a7eb7 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/Cargo.toml @@ -0,0 +1,41 @@ +[package] +edition = "2024" +name = "embassy-stm32u5-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" +publish = false + +[dependencies] +# Change stm32u5g9zj to your chip name, if necessary. +embassy-stm32 = { version = "0.6.0", path = "../../embassy-stm32", features = ["defmt", "unstable-pac", "stm32u545re", "time-driver-any", "memory-x" ] } +embassy-sync = { version = "0.8.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.10.0", path = "../../embassy-executor", features = ["platform-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.5.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.6.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.2", path = "../../embassy-futures" } + +defmt = "1.0.1" +defmt-rtt = "1.0.0" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +heapless = { version = "0.9", default-features = false } +embedded-graphics = { version = "0.8.1" } +tinybmp = { version = "0.6.0" } + +micromath = "2.0.0" +ssd1306 = "0.10.0" + +[features] +## Use secure registers when TrustZone is enabled +trustzone-secure = ["embassy-stm32/trustzone-secure"] + +[profile.release] +debug = 2 + +[package.metadata.embassy] +build = [ + { target = "thumbv8m.main-none-eabihf", artifact-dir = "out/examples/stm32u5" } +] diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/Cod_esp/Cod_esp.ino b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/Cod_esp/Cod_esp.ino new file mode 100644 index 0000000000..89008e6484 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/Cod_esp/Cod_esp.ino @@ -0,0 +1,111 @@ +#include +#include +#include + +const char* SSID = "DIGI-xMu4"; +const char* PASS = "hPzDTzj9xF"; + +WebServer server(80); +WebSocketsServer webSocket(81); +String linie = ""; + +const char* PAGE = R"HTML( + + +3DSpire MIDI + + +

3DSpire - MIDI Controller

+
conectare...
+ +
-
+
Volum: -
+
+
+ + +)HTML"; + +void handleRoot(){ server.send(200, "text/html", PAGE); } +void onWsEvent(uint8_t num, WStype_t type, uint8_t*, size_t){ + if(type==WStype_CONNECTED) Serial.printf("Browser conectat (client %u)\n", num); + if(type==WStype_DISCONNECTED) Serial.printf("Browser deconectat (client %u)\n", num); +} + +void setup(){ + Serial.begin(115200); + Serial2.begin(115200, SERIAL_8N1, 16, 17); // 16=RX (de la STM32 PA9), 17=TX + WiFi.mode(WIFI_STA); WiFi.begin(SSID, PASS); + while(WiFi.status()!=WL_CONNECTED){ delay(500); Serial.print("."); } + Serial.print("\nDeschide in browser: http://"); Serial.println(WiFi.localIP()); + server.on("/", handleRoot); server.begin(); + webSocket.begin(); webSocket.onEvent(onWsEvent); +} + +void loop(){ + server.handleClient(); + webSocket.loop(); + while(Serial2.available()){ + char c = Serial2.read(); + if(c=='\n'){ + if(linie.length()){ + webSocket.broadcastTXT(linie); + Serial.print("->WS: "); Serial.println(linie); + linie = ""; + } + } else if(c!='\r'){ + linie += c; + } + } +} \ No newline at end of file diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/Cod_esp/Cod_esp.txt b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/Cod_esp/Cod_esp.txt new file mode 100644 index 0000000000..aa65ddcb3c --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/Cod_esp/Cod_esp.txt @@ -0,0 +1,111 @@ +#include +#include +#include + +const char* SSID = "DIGI-Z6yk"; +const char* PASS = "Sf34ZkkTcb"; + +WebServer server(80); +WebSocketsServer webSocket(81); +String linie = ""; + +const char* PAGE = R"HTML( + + +3DSpire MIDI + + +

3DSpire - MIDI Controller

+
conectare...
+ +
-
+
Volum: -
+
+
+ + +)HTML"; + +void handleRoot(){ server.send(200, "text/html", PAGE); } +void onWsEvent(uint8_t num, WStype_t type, uint8_t*, size_t){ + if(type==WStype_CONNECTED) Serial.printf("Browser conectat (client %u)\n", num); + if(type==WStype_DISCONNECTED) Serial.printf("Browser deconectat (client %u)\n", num); +} + +void setup(){ + Serial.begin(115200); + Serial2.begin(115200, SERIAL_8N1, 16, 17); // 16=RX (de la STM32 PA9), 17=TX + WiFi.mode(WIFI_STA); WiFi.begin(SSID, PASS); + while(WiFi.status()!=WL_CONNECTED){ delay(500); Serial.print("."); } + Serial.print("\nDeschide in browser: http://"); Serial.println(WiFi.localIP()); + server.on("/", handleRoot); server.begin(); + webSocket.begin(); webSocket.onEvent(onWsEvent); +} + +void loop(){ + server.handleClient(); + webSocket.loop(); + while(Serial2.available()){ + char c = Serial2.read(); + if(c=='\n'){ + if(linie.length()){ + webSocket.broadcastTXT(linie); + Serial.print("->WS: "); Serial.println(linie); + linie = ""; + } + } else if(c!='\r'){ + linie += c; + } + } +} \ No newline at end of file diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/build.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/build.rs new file mode 100644 index 0000000000..7da9d291a2 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/adc.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/adc.rs new file mode 100644 index 0000000000..ce1308544a --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/adc.rs @@ -0,0 +1,117 @@ +#![no_std] +#![no_main] + +use defmt::*; +use defmt_rtt as _; +use embassy_stm32::adc::{self, Adc, AdcChannel, AdcConfig, SampleTime, adc4}; +use embassy_stm32::{bind_interrupts, dma, peripherals}; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + GPDMA1_CHANNEL0 => dma::InterruptHandler; + GPDMA1_CHANNEL1 => dma::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let config = embassy_stm32::Config::default(); + + let mut p = embassy_stm32::init(config); + + // **** ADC1 init **** + let mut config = AdcConfig::default(); + config.averaging = Some(adc::Averaging::Samples1024); + config.resolution = Some(adc::Resolution::Bits14); + let mut adc1 = Adc::new_with_config(p.ADC1, config); + let mut adc1_pin1 = p.PA3; // A0 on nucleo u5a5 + let mut adc1_pin2 = p.PA2; // A1 + let max1 = adc::resolution_to_max_count(adc::Resolution::Bits14); + + // **** ADC2 init **** + let mut config = AdcConfig::default(); + config.averaging = Some(adc::Averaging::Samples1024); + config.resolution = Some(adc::Resolution::Bits14); + let mut adc2 = Adc::new_with_config(p.ADC2, config); + let mut adc2_pin1 = p.PC3; // A2 + let mut adc2_pin2 = p.PB0; // A3 + let max2 = adc::resolution_to_max_count(adc::Resolution::Bits14); + + // **** ADC4 init **** + let mut adc4 = Adc::new_adc4(p.ADC4); + let mut adc4_pin1 = p.PC1; // A4 + let mut adc4_pin2 = p.PC0; // A5 + adc4.set_resolution_adc4(adc4::Resolution::Bits12); + adc4.set_averaging_adc4(adc4::Averaging::Samples256); + let max4 = adc4::resolution_to_max_count(adc4::Resolution::Bits12); + + // **** ADC1 blocking read **** + let raw: u16 = adc1.blocking_read(&mut adc1_pin1, SampleTime::Cycles1605); + let volt: f32 = 3.3 * raw as f32 / max1 as f32; + info!("Read adc1 pin 1 {}", volt); + + let raw: u16 = adc1.blocking_read(&mut adc1_pin2, SampleTime::Cycles1605); + let volt: f32 = 3.3 * raw as f32 / max1 as f32; + info!("Read adc1 pin 2 {}", volt); + + // **** ADC2 blocking read **** + let raw: u16 = adc2.blocking_read(&mut adc2_pin1, SampleTime::Cycles1605); + let volt: f32 = 3.3 * raw as f32 / max2 as f32; + info!("Read adc2 pin 1 {}", volt); + + let raw: u16 = adc2.blocking_read(&mut adc2_pin2, SampleTime::Cycles1605); + let volt: f32 = 3.3 * raw as f32 / max2 as f32; + info!("Read adc2 pin 2 {}", volt); + + // **** ADC4 blocking read **** + let raw: u16 = adc4.blocking_read(&mut adc4_pin1, adc4::SampleTime::Cycles15); + let volt: f32 = 3.3 * raw as f32 / max4 as f32; + info!("Read adc4 pin 1 {}", volt); + + let raw: u16 = adc4.blocking_read(&mut adc4_pin2, adc4::SampleTime::Cycles15); + let volt: f32 = 3.3 * raw as f32 / max4 as f32; + info!("Read adc4 pin 2 {}", volt); + + // **** ADC1 async read **** + let mut measurements = [0u16; 2]; + + adc1.read( + p.GPDMA1_CH0.reborrow(), + Irqs, + [ + (adc1_pin1.reborrow_adc(), adc::SampleTime::Cycles1605), + (adc1_pin2.reborrow_adc(), adc::SampleTime::Cycles1605), + ] + .into_iter(), + None, + &mut measurements, + ) + .await; + let volt1: f32 = 3.3 * measurements[0] as f32 / max1 as f32; + let volt2: f32 = 3.3 * measurements[1] as f32 / max1 as f32; + + info!("Async read 1 pin 1 {}", volt1); + info!("Async read 1 pin 2 {}", volt2); + + // **** ADC2 does not support async read **** + + // **** ADC4 async read **** + let mut measurements = [0u16; 2]; + + // The channels must be in ascending order and can't repeat for ADC4 + adc4.read( + p.GPDMA1_CH1.reborrow(), + Irqs, + [ + (adc4_pin1.reborrow_adc(), adc4::SampleTime::Cycles15), + (adc4_pin2.reborrow_adc(), adc4::SampleTime::Cycles15), + ] + .into_iter(), + None, + &mut measurements, + ) + .await; + let volt2: f32 = 3.3 * measurements[0] as f32 / max4 as f32; + let volt1: f32 = 3.3 * measurements[1] as f32 / max4 as f32; + info!("Async read 4 pin 1 {}", volt1); + info!("Async read 4 pin 2 {}", volt2); +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/blinky.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/blinky.rs new file mode 100644 index 0000000000..7800ab1dee --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/blinky.rs @@ -0,0 +1,20 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let mut led = Output::new(p.PA5, Level::High, Speed::Low); + + loop { + led.set_high(); + Timer::after_millis(100).await; + led.set_low(); + Timer::after_millis(100).await; + } +} \ No newline at end of file diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/boot.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/boot.rs new file mode 100644 index 0000000000..029a554eef --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/boot.rs @@ -0,0 +1,16 @@ +#![no_std] +#![no_main] + +use defmt::*; +use defmt_rtt as _; +use embassy_stm32 as _; +use panic_probe as _; + +#[cortex_m_rt::entry] +fn main() -> ! { + info!("Hello World!"); + + loop { + //defmt::info!("loop!"); + } +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/can.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/can.rs new file mode 100644 index 0000000000..0379d03f8a --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/can.rs @@ -0,0 +1,96 @@ +#![no_std] +#![no_main] + +// CAN example for the STM32U5, in internal loopback mode. +// Tested on a NUCLEO-U545RE-Q; builds under this directory's stm32u5g9zj feature. + +use defmt::{error, info}; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::can::{CanConfigurator, Frame, IT0InterruptHandler, IT1InterruptHandler, filter}; +use embassy_stm32::{Config, bind_interrupts, peripherals}; +use embassy_time::Timer; +use panic_probe as _; + +// FDCAN1 has 2 interrupt lines which must be bound + +// Manual: rm0456, pg 3023/3651 +bind_interrupts!(struct Irqs { + FDCAN1_IT0 => IT0InterruptHandler; + FDCAN1_IT1 => IT1InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + // The HSE crystal is not populated on the MB1841 board (see UM3062), so the system + // and FDCAN are clocked from the internal 16 MHz HSI via PLL1 + config.rcc.hsi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::Hsi, + prediv: PllPreDiv::Div1, // 16 MHz + mul: PllMul::Mul10, // VCO = 16 × 10 = 160 MHz + divp: None, // unused + divq: Some(PllDiv::Div4), // 40 MHz FDCAN kernel clock + divr: Some(PllDiv::Div1), // 160 MHz for the CPU + }); + + config.rcc.sys = Sysclk::Pll1R; + config.rcc.mux.fdcan1sel = mux::Fdcansel::Pll1Q; + } + + let p = embassy_stm32::init(config); + info!("Device started"); + + let mut can = CanConfigurator::new(p.FDCAN1, p.PA11, p.PA12, Irqs); + + // Standard CAN bit rate. Every node on a bus must use the same one — + // 125k / 250k / 500k / 1M are the common choices. + can.set_bitrate(250_000); + + can.properties().set_extended_filter( + filter::ExtendedFilterSlot::_0, + filter::ExtendedFilter::accept_all_into_fifo1(), + ); + + // Internal loopback: TX is looped to RX inside the chip. + // External loopback: TX is driven on the pin and looped back through a transceiver + // Normal mode: needs a transceiver AND a second node to ACK. + let mut can = can.into_internal_loopback_mode(); + info!("CAN configured !"); + + let mut i = 0; + let mut last_read_ts = embassy_time::Instant::now(); + + loop { + info!("Writing frame..."); + // Arbitrary 29-bit extended ID. On a real bus the ID also sets priority (lower = higher). + let frame = Frame::new_extended(0x123456F, &[i; 8]).unwrap(); + can.write(&frame).await; + + match can.read().await { + Ok(envelope) => { + let (ts, rx_frame) = (envelope.ts, envelope.frame); + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + + info!( + "Rx: {} {:02x} --- {}ms", + rx_frame.header().len(), // Number of data bytes + rx_frame.data()[0..rx_frame.header().len() as usize], // Slice of the valid payload + delta, + ) + } + Err(err) => error!("Error in frame: {}", err), + } + + Timer::after_millis(250).await; + + i += 1; + if i > 2 { + break; + } + } +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/can_split.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/can_split.rs new file mode 100644 index 0000000000..6c54d91587 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/can_split.rs @@ -0,0 +1,110 @@ +#![no_std] +#![no_main] + +// CAN example for the STM32U5 using split() to drive TX and RX from separate tasks. +// Tested on a NUCLEO-U545RE-Q; builds under this directory's stm32u5g9zj feature. +use defmt::{error, info}; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::can::{CanConfigurator, CanRx, CanTx, Frame, IT0InterruptHandler, IT1InterruptHandler, filter}; +use embassy_stm32::{Config, bind_interrupts, peripherals}; +use embassy_time::{Instant, Timer}; +use panic_probe as _; + +// FDCAN1 has 2 interrupt lines which must be bound +// Manual: rm0456, pg 3023/3651 +bind_interrupts!(struct Irqs { + FDCAN1_IT0 => IT0InterruptHandler; + FDCAN1_IT1 => IT1InterruptHandler; +}); + +#[embassy_executor::task] +async fn writer(mut tx: CanTx<'static>) { + let mut i = 0; + let start = Instant::now(); + loop { + info!("Writing frame..."); + // Arbitrary 29-bit extended ID. On a real bus the ID also sets priority (lower = higher). + let frame = Frame::new_extended(0x123456F, &[i; 8]).unwrap(); + tx.write(&frame).await; + let elapsed_ms = start.elapsed().as_millis(); // Time since the task has started + info!("tx: Sent frame {} at: {} ms", i, elapsed_ms); + + i += 1; + if i > 3 { + break; + } + // Keep the task from blocking the MCU + Timer::after_millis(250).await; + } +} + +#[embassy_executor::task] +async fn reader(mut rx: CanRx<'static>) { + loop { + match rx.read().await { + Ok(envelope) => info!("Rx: Received {:02x}", envelope.frame.data()), // Slice of the valid payload + Err(_err) => error!("rx error"), + } + + // Keep the task from blocking the MCU + Timer::after_millis(250).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + // The HSE crystal is not populated on the MB1841 board (see UM3062), so the system + // and FDCAN are clocked from the internal 16 MHz HSI via PLL1 + config.rcc.hsi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::Hsi, + prediv: PllPreDiv::Div1, // 16 MHz + mul: PllMul::Mul10, // VCO = 16 × 10 = 160 MHz + divp: None, // unused + divq: Some(PllDiv::Div4), // 40 MHz FDCAN kernel clock + divr: Some(PllDiv::Div1), // 160 MHz for the CPU + }); + + config.rcc.sys = Sysclk::Pll1R; + config.rcc.mux.fdcan1sel = mux::Fdcansel::Pll1Q; + } + + let p = embassy_stm32::init(config); + info!("Device started"); + + let mut can = CanConfigurator::new(p.FDCAN1, p.PA11, p.PA12, Irqs); + + // Standard CAN bit rate. Every node on a bus must use the same one — + // 125k / 250k / 500k / 1M are the common choices. + can.set_bitrate(250_000); + + // Accept every frame into the RX FIFO. Without a filter, the peripheral discards + // all incoming frames, so read() would block forever. + can.properties().set_extended_filter( + filter::ExtendedFilterSlot::_0, + filter::ExtendedFilter::accept_all_into_fifo1(), + ); + + // Internal loopback: TX is looped to RX inside the chip. + // External loopback: TX is driven on the pin and looped back through a transceiver + // Normal mode: needs a transceiver AND a second node to ACK. + let can = can.into_internal_loopback_mode(); + info!("CAN configured !"); + + // if read and write capabilities need to be divided across tasks, use .split() to get owned values that can be passed to tasks + // split() hands ownership of TX and RX to separate tasks, so neither needs a Mutex to share one Can — the point of the function. + + let (tx, rx, _properties) = can.split(); + + spawner.spawn(writer(tx).unwrap()); + spawner.spawn(reader(rx).unwrap()); + + // Keep main alive while tasks are polled + loop { + Timer::after_millis(100).await; + } +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/dac.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/dac.rs new file mode 100644 index 0000000000..399af97302 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/dac.rs @@ -0,0 +1,38 @@ +//! DAC implemented on the A2 pin(PA4), connected to an LED + +#![no_std] +#![no_main] +use defmt::*; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::Config; +use embassy_stm32::dac::DacChannel; +use embassy_stm32::rcc::{LsConfig, mux}; +use embassy_time::Timer; +use panic_probe as _; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Device has started"); + let mut config = Config::default(); + + // We need the internal LSI(Low Speed Internal oscillator) for DAC sync + config.rcc.ls = LsConfig::default_lsi(); + + // changing the mux to point to our clock(LSI) + config.rcc.mux.dac1sel = mux::Dacsel::Lsi; + let p = embassy_stm32::init(config); + + info!("Board connected!"); + + // As per the reference manual for the STM32U5, the PINs PA4 and PA5 can be used as DACs + // PA4 is used for the channel 1 of the dac(p.DAC1), while PA5 is used for the second + let mut dac = DacChannel::new_blocking(p.DAC1, p.PA4); + + // The values accepted are between 0 and 255, 100 should make the LED half lit + dac.set(100); + + loop { + Timer::after_millis(5000).await; + } +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/dac_dma.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/dac_dma.rs new file mode 100644 index 0000000000..e503e16483 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/dac_dma.rs @@ -0,0 +1,59 @@ +//! DAC Example tested on the stm32u545re board, DAC pin connected to an external LED + +#![no_std] +#![no_main] +use defmt::*; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::dac::DacChannel; +use embassy_stm32::peripherals::GPDMA1_CH0; +use embassy_stm32::rcc::{LsConfig, enable_and_reset, mux}; +use embassy_stm32::time::Hertz; +use embassy_stm32::timer::low_level::RoundTo::Faster; +use embassy_stm32::timer::low_level::{MasterMode, Timer}; +use embassy_stm32::triggers::TIM6_TRGO; +use embassy_stm32::{Config, bind_interrupts, dma}; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + GPDMA1_CHANNEL0 => dma::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Device has started"); + let mut config = Config::default(); + + // turns on internal LSI, needed for DAC sync, this is a low power (optional) feature to operate in sample and hold mode + config.rcc.ls = LsConfig::default_lsi(); + // changing the mux to point to our clock(LSI) + config.rcc.mux.dac1sel = mux::Dacsel::Lsi; + let p = embassy_stm32::init(config); + + info!("Board connected!"); + + // Any timer that supports the hardware TRGO can be selected here + let mut dac = DacChannel::new_triggered(p.DAC1, p.GPDMA1_CH0, TIM6_TRGO, Irqs, p.PA4); + + enable_and_reset::(); + + // The timer is needed such that the DMA knows when to "fire" + // DMA is demand-pulled by the peripheral, but the DAC does not have a native "demand", that's + // why we need the TRGO trigger event + let timer = Timer::new(p.TIM6); + + // Hertz value chosen randomly + timer.set_frequency(Hertz(10000), Faster); + + // DAC listens to MMS + timer.set_master_mode(MasterMode::Update); + + timer.start(); + + let mut i = 0; + + loop { + dac.write(&[i]).await; + i = i.wrapping_add(1); + } +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/fd_can.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/fd_can.rs new file mode 100644 index 0000000000..73de101dc0 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/fd_can.rs @@ -0,0 +1,97 @@ +#![no_std] +#![no_main] + +// FDCAN example for the STM32U5, in internal loopback mode. +// Tested on a NUCLEO-U545RE-Q; builds under this directory's stm32u5g9zj feature. + +use defmt::{error, info}; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::can::{CanConfigurator, IT0InterruptHandler, IT1InterruptHandler, filter, frame}; +use embassy_stm32::{Config, bind_interrupts, peripherals}; +use embassy_time::Timer; +use panic_probe as _; + +// FDCAN1 has 2 interrupt lines which must be bound +// Manual: rm0456, pg 3023/3651 +bind_interrupts!(struct Irqs { + FDCAN1_IT0 => IT0InterruptHandler; + FDCAN1_IT1 => IT1InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + // The HSE crystal is not populated on the MB1841 board (see UM3062), so the system + // and FDCAN are clocked from the internal 16 MHz HSI via PLL1 + config.rcc.hsi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::Hsi, + prediv: PllPreDiv::Div1, // 16 MHz + mul: PllMul::Mul10, // VCO = 16 × 10 = 160 MHz + divp: None, // unused + divq: Some(PllDiv::Div4), // 40 MHz FDCAN kernel clock + divr: Some(PllDiv::Div1), // 160 MHz for the CPU + }); + + config.rcc.sys = Sysclk::Pll1R; + config.rcc.mux.fdcan1sel = mux::Fdcansel::Pll1Q; + } + + let p = embassy_stm32::init(config); + info!("Device started"); + + let mut can = CanConfigurator::new(p.FDCAN1, p.PA11, p.PA12, Irqs); + + // Standard CAN bit rate. Every node on a bus must use the same one — + // 125k / 250k / 500k / 1M are the common choices. + can.set_bitrate(250_000); + // false disables bit-rate switching (BRS), so frames use the nominal rate throughout + can.set_fd_data_bitrate(1_000_000, false); + + can.properties().set_extended_filter( + filter::ExtendedFilterSlot::_0, + filter::ExtendedFilter::accept_all_into_fifo1(), + ); + + // Internal loopback: TX is looped to RX inside the chip. + // External loopback: TX is driven on the pin and looped back through a transceiver + // Normal mode: needs a transceiver AND a second node to ACK. + let mut can = can.into_internal_loopback_mode(); + info!("CAN configured !"); + + let mut i = 0; + let mut last_read_ts = embassy_time::Instant::now(); + + loop { + info!("Writing frame using FD Api"); + // Arbitrary 29-bit extended ID. On a real bus the ID also sets priority (lower = higher). + // FD frames carry up to 64 data bytes, vs. 8 for classic CAN. + let frame = frame::FdFrame::new_extended(0x123456F, &[i; 64]).unwrap(); + can.write_fd(&frame).await; + + match can.read_fd().await { + Ok(envelope) => { + let (ts, rx_frame) = (envelope.ts, envelope.frame); + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {} {:02x} --- using FD API {}ms", + rx_frame.header().len(), // Number of data bytes + rx_frame.data()[0..rx_frame.header().len() as usize], // Slice of the valid payload + delta, + ) + } + Err(err) => error!("Error in frame: {}", err), + } + + i += 1; + if i > 4 { + break; + } + + Timer::after_millis(250).await; + } +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/ferris.bmp b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/ferris.bmp new file mode 100644 index 0000000000..7a222ab84d Binary files /dev/null and b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/ferris.bmp differ diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/flash.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/flash.rs new file mode 100644 index 0000000000..14d831e423 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/flash.rs @@ -0,0 +1,56 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::flash::Flash; +use embassy_time::Timer; +use panic_probe as _; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello Flash!"); + + const ADDR: u32 = 0x8_0000; // This is the offset into the third region, the absolute address is 4x32K + 128K + 0x8_0000. + + // wait a bit before accessing the flash + Timer::after_millis(300).await; + + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank1_region; + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Erasing..."); + unwrap!(f.blocking_erase(ADDR, ADDR + 256 * 1024)); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read after erase: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!(f.blocking_write( + ADDR, + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + )); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + assert_eq!( + &buf[..], + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + ); +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/hspi_memory_mapped.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/hspi_memory_mapped.rs new file mode 100644 index 0000000000..d3b7918f9b --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/hspi_memory_mapped.rs @@ -0,0 +1,461 @@ +#![no_main] +#![no_std] + +// Tested on an STM32U5G9J-DK2 demo board using the on-board MX66LM1G45G flash memory +// The flash is connected to the HSPI1 port as an OCTA-DTR device +// +// Use embassy-stm32 feature "stm32u5g9zj" and probe-rs chip "STM32U5G9ZJTxQ" + +use defmt::info; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::hspi::{ + AddressSize, ChipSelectHighTime, DummyCycles, FIFOThresholdLevel, Hspi, HspiWidth, Instance, MemorySize, + MemoryType, TransferConfig, WrapSize, +}; +use embassy_stm32::mode::Async; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, dma, peripherals, rcc}; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + GPDMA1_CHANNEL7 => dma::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Start hspi_memory_mapped"); + + // RCC config + let mut config = embassy_stm32::Config::default(); + config.rcc.hse = Some(rcc::Hse { + freq: Hertz(16_000_000), + mode: rcc::HseMode::Oscillator, + }); + config.rcc.pll1 = Some(rcc::Pll { + source: rcc::PllSource::Hse, + prediv: rcc::PllPreDiv::Div1, + mul: rcc::PllMul::Mul10, + divp: None, + divq: None, + divr: Some(rcc::PllDiv::Div1), + }); + config.rcc.sys = rcc::Sysclk::Pll1R; // 160 Mhz + config.rcc.pll2 = Some(rcc::Pll { + source: rcc::PllSource::Hse, + prediv: rcc::PllPreDiv::Div4, + mul: rcc::PllMul::Mul66, + divp: None, + divq: Some(rcc::PllDiv::Div2), + divr: None, + }); + config.rcc.mux.hspi1sel = rcc::mux::Hspisel::Pll2Q; // 132 MHz + + // Initialize peripherals + let p = embassy_stm32::init(config); + + let flash_config = embassy_stm32::hspi::Config { + fifo_threshold: FIFOThresholdLevel::_4Bytes, + memory_type: MemoryType::Macronix, + device_size: MemorySize::_1GiB, + chip_select_high_time: ChipSelectHighTime::_2Cycle, + free_running_clock: false, + clock_mode: false, + wrap_size: WrapSize::None, + clock_prescaler: 0, + sample_shifting: false, + delay_hold_quarter_cycle: false, + chip_select_boundary: 0, + delay_block_bypass: false, + max_transfer: 0, + refresh: 0, + }; + + let use_dma = true; + + info!("Testing flash in OCTA DTR mode and memory mapped mode"); + + let hspi = Hspi::new_octospi( + p.HSPI1, + p.PI3, + p.PH10, + p.PH11, + p.PH12, + p.PH13, + p.PH14, + p.PH15, + p.PI0, + p.PI1, + p.PH9, + p.PI2, + p.GPDMA1_CH7, + Irqs, + flash_config, + ); + + let mut flash = OctaDtrFlashMemory::new(hspi).await; + + let flash_id = flash.read_id(); + info!("FLASH ID: {=[u8]:x}", flash_id); + + let mut rd_buf = [0u8; 16]; + flash.read_memory(0, &mut rd_buf, use_dma).await; + info!("READ BUF: {=[u8]:#X}", rd_buf); + + flash.erase_sector(0).await; + flash.read_memory(0, &mut rd_buf, use_dma).await; + info!("READ BUF: {=[u8]:#X}", rd_buf); + assert_eq!(rd_buf[0], 0xFF); + assert_eq!(rd_buf[15], 0xFF); + + let mut wr_buf = [0u8; 16]; + for i in 0..wr_buf.len() { + wr_buf[i] = i as u8; + } + info!("WRITE BUF: {=[u8]:#X}", wr_buf); + flash.write_memory(0, &wr_buf, use_dma).await; + flash.read_memory(0, &mut rd_buf, use_dma).await; + info!("READ BUF: {=[u8]:#X}", rd_buf); + assert_eq!(rd_buf[0], 0x00); + assert_eq!(rd_buf[15], 0x0F); + + flash.enable_mm().await; + info!("Enabled memory mapped mode"); + + let first_u32 = unsafe { *(0xA0000000 as *const u32) }; + info!("first_u32: 0x{=u32:X}", first_u32); + assert_eq!(first_u32, 0x03020100); + + let second_u32 = unsafe { *(0xA0000004 as *const u32) }; + assert_eq!(second_u32, 0x07060504); + info!("second_u32: 0x{=u32:X}", second_u32); + + let first_u8 = unsafe { *(0xA0000000 as *const u8) }; + assert_eq!(first_u8, 00); + info!("first_u8: 0x{=u8:X}", first_u8); + + let second_u8 = unsafe { *(0xA0000001 as *const u8) }; + assert_eq!(second_u8, 0x01); + info!("second_u8: 0x{=u8:X}", second_u8); + + let third_u8 = unsafe { *(0xA0000002 as *const u8) }; + assert_eq!(third_u8, 0x02); + info!("third_u8: 0x{=u8:X}", third_u8); + + let fourth_u8 = unsafe { *(0xA0000003 as *const u8) }; + assert_eq!(fourth_u8, 0x03); + info!("fourth_u8: 0x{=u8:X}", fourth_u8); + + info!("DONE"); +} + +// Custom implementation for MX66UW1G45G NOR flash memory from Macronix. +// Chip commands are hardcoded as they depend on the chip used. +// This implementation enables Octa I/O (OPI) and Double Transfer Rate (DTR) + +pub struct OctaDtrFlashMemory<'d, I: Instance> { + hspi: Hspi<'d, I, Async>, +} + +impl<'d, I: Instance> OctaDtrFlashMemory<'d, I> { + const MEMORY_PAGE_SIZE: usize = 256; + + const CMD_READ_OCTA_DTR: u16 = 0xEE11; + const CMD_PAGE_PROGRAM_OCTA_DTR: u16 = 0x12ED; + + const CMD_READ_ID_OCTA_DTR: u16 = 0x9F60; + + const CMD_RESET_ENABLE: u8 = 0x66; + const CMD_RESET_ENABLE_OCTA_DTR: u16 = 0x6699; + const CMD_RESET: u8 = 0x99; + const CMD_RESET_OCTA_DTR: u16 = 0x9966; + + const CMD_WRITE_ENABLE: u8 = 0x06; + const CMD_WRITE_ENABLE_OCTA_DTR: u16 = 0x06F9; + + const CMD_SECTOR_ERASE_OCTA_DTR: u16 = 0x21DE; + const CMD_BLOCK_ERASE_OCTA_DTR: u16 = 0xDC23; + + const CMD_READ_SR: u8 = 0x05; + const CMD_READ_SR_OCTA_DTR: u16 = 0x05FA; + + const CMD_READ_CR2: u8 = 0x71; + const CMD_WRITE_CR2: u8 = 0x72; + + const CR2_REG1_ADDR: u32 = 0x00000000; + const CR2_OCTA_DTR: u8 = 0x02; + + const CR2_REG3_ADDR: u32 = 0x00000300; + const CR2_DC_6_CYCLES: u8 = 0x07; + + pub async fn new(hspi: Hspi<'d, I, Async>) -> Self { + let mut memory = Self { hspi }; + + memory.reset_memory().await; + memory.enable_octa_dtr().await; + memory + } + + async fn enable_octa_dtr(&mut self) { + self.write_enable_spi().await; + self.write_cr2_spi(Self::CR2_REG3_ADDR, Self::CR2_DC_6_CYCLES); + self.write_enable_spi().await; + self.write_cr2_spi(Self::CR2_REG1_ADDR, Self::CR2_OCTA_DTR); + } + + pub async fn enable_mm(&mut self) { + let read_config = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(Self::CMD_READ_OCTA_DTR as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + dummy: DummyCycles::_6, + ..Default::default() + }; + + let write_config = TransferConfig { + iwidth: HspiWidth::OCTO, + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + ..Default::default() + }; + self.hspi.enable_memory_mapped_mode(read_config, write_config).unwrap(); + } + + async fn exec_command_spi(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: HspiWidth::SING, + instruction: Some(cmd as u32), + ..Default::default() + }; + info!("Excuting command: 0x{:X}", transaction.instruction.unwrap()); + self.hspi.blocking_command(&transaction).unwrap(); + } + + async fn exec_command_octa_dtr(&mut self, cmd: u16) { + let transaction = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(cmd as u32), + isize: AddressSize::_16Bit, + idtr: true, + ..Default::default() + }; + info!("Excuting command: 0x{:X}", transaction.instruction.unwrap()); + self.hspi.blocking_command(&transaction).unwrap(); + } + + fn wait_write_finish_spi(&mut self) { + while (self.read_sr_spi() & 0x01) != 0 {} + } + + fn wait_write_finish_octa_dtr(&mut self) { + while (self.read_sr_octa_dtr() & 0x01) != 0 {} + } + + pub async fn reset_memory(&mut self) { + // servono entrambi i comandi? + self.exec_command_octa_dtr(Self::CMD_RESET_ENABLE_OCTA_DTR).await; + self.exec_command_octa_dtr(Self::CMD_RESET_OCTA_DTR).await; + self.exec_command_spi(Self::CMD_RESET_ENABLE).await; + self.exec_command_spi(Self::CMD_RESET).await; + self.wait_write_finish_spi(); + } + + async fn write_enable_spi(&mut self) { + self.exec_command_spi(Self::CMD_WRITE_ENABLE).await; + } + + async fn write_enable_octa_dtr(&mut self) { + self.exec_command_octa_dtr(Self::CMD_WRITE_ENABLE_OCTA_DTR).await; + } + + pub fn read_id(&mut self) -> [u8; 3] { + let mut buffer = [0; 6]; + let transaction: TransferConfig = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(Self::CMD_READ_ID_OCTA_DTR as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + address: Some(0), + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + dummy: DummyCycles::_5, + ..Default::default() + }; + info!("Reading flash id: 0x{:X}", transaction.instruction.unwrap()); + self.hspi.blocking_read(&mut buffer, transaction).unwrap(); + [buffer[0], buffer[2], buffer[4]] + } + + pub async fn read_memory(&mut self, addr: u32, buffer: &mut [u8], use_dma: bool) { + let transaction = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(Self::CMD_READ_OCTA_DTR as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + address: Some(addr), + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + dummy: DummyCycles::_6, + ..Default::default() + }; + if use_dma { + self.hspi.read(buffer, transaction).await.unwrap(); + } else { + self.hspi.blocking_read(buffer, transaction).unwrap(); + } + } + + async fn perform_erase_octa_dtr(&mut self, addr: u32, cmd: u16) { + let transaction = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(cmd as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + address: Some(addr), + adsize: AddressSize::_32Bit, + addtr: true, + ..Default::default() + }; + self.write_enable_octa_dtr().await; + self.hspi.blocking_command(&transaction).unwrap(); + self.wait_write_finish_octa_dtr(); + } + + pub async fn erase_sector(&mut self, addr: u32) { + info!("Erasing 4K sector at address: 0x{:X}", addr); + self.perform_erase_octa_dtr(addr, Self::CMD_SECTOR_ERASE_OCTA_DTR).await; + } + + pub async fn erase_block(&mut self, addr: u32) { + info!("Erasing 64K block at address: 0x{:X}", addr); + self.perform_erase_octa_dtr(addr, Self::CMD_BLOCK_ERASE_OCTA_DTR).await; + } + + async fn write_page_octa_dtr(&mut self, addr: u32, buffer: &[u8], len: usize, use_dma: bool) { + assert!( + (len as u32 + (addr & 0x000000ff)) <= Self::MEMORY_PAGE_SIZE as u32, + "write_page(): page write length exceeds page boundary (len = {}, addr = {:X}", + len, + addr + ); + + let transaction = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(Self::CMD_PAGE_PROGRAM_OCTA_DTR as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + address: Some(addr), + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + ..Default::default() + }; + self.write_enable_octa_dtr().await; + if use_dma { + self.hspi.write(buffer, transaction).await.unwrap(); + } else { + self.hspi.blocking_write(buffer, transaction).unwrap(); + } + self.wait_write_finish_octa_dtr(); + } + + pub async fn write_memory(&mut self, addr: u32, buffer: &[u8], use_dma: bool) { + let mut left = buffer.len(); + let mut place = addr; + let mut chunk_start = 0; + + while left > 0 { + let max_chunk_size = Self::MEMORY_PAGE_SIZE - (place & 0x000000ff) as usize; + let chunk_size = if left >= max_chunk_size { max_chunk_size } else { left }; + let chunk = &buffer[chunk_start..(chunk_start + chunk_size)]; + self.write_page_octa_dtr(place, chunk, chunk_size, use_dma).await; + place += chunk_size as u32; + left -= chunk_size; + chunk_start += chunk_size; + } + } + + pub fn read_sr_spi(&mut self) -> u8 { + let mut buffer = [0; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: HspiWidth::SING, + instruction: Some(Self::CMD_READ_SR as u32), + dwidth: HspiWidth::SING, + ..Default::default() + }; + self.hspi.blocking_read(&mut buffer, transaction).unwrap(); + // info!("Read MX66LM1G45G SR register: 0x{:x}", buffer[0]); + buffer[0] + } + + pub fn read_sr_octa_dtr(&mut self) -> u8 { + let mut buffer = [0; 2]; + let transaction: TransferConfig = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(Self::CMD_READ_SR_OCTA_DTR as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + address: Some(0), + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + dummy: DummyCycles::_5, + ..Default::default() + }; + self.hspi.blocking_read(&mut buffer, transaction).unwrap(); + // info!("Read MX66LM1G45G SR register: 0x{:x}", buffer[0]); + buffer[0] + } + + pub fn read_cr2_spi(&mut self, addr: u32) -> u8 { + let mut buffer = [0; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: HspiWidth::SING, + instruction: Some(Self::CMD_READ_CR2 as u32), + adwidth: HspiWidth::SING, + address: Some(addr), + adsize: AddressSize::_32Bit, + dwidth: HspiWidth::SING, + ..Default::default() + }; + self.hspi.blocking_read(&mut buffer, transaction).unwrap(); + // info!("Read MX66LM1G45G CR2[0x{:X}] register: 0x{:x}", addr, buffer[0]); + buffer[0] + } + + pub fn write_cr2_spi(&mut self, addr: u32, value: u8) { + let buffer = [value; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: HspiWidth::SING, + instruction: Some(Self::CMD_WRITE_CR2 as u32), + adwidth: HspiWidth::SING, + address: Some(addr), + adsize: AddressSize::_32Bit, + dwidth: HspiWidth::SING, + ..Default::default() + }; + self.hspi.blocking_write(&buffer, transaction).unwrap(); + } +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/i2c.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/i2c.rs new file mode 100644 index 0000000000..523bced678 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/i2c.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::i2c::I2c; +use panic_probe as _; + +const HTS221_ADDRESS: u8 = 0x5F; +const WHOAMI: u8 = 0x0F; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let mut i2c = I2c::new_blocking(p.I2C2, p.PF1, p.PF0, Default::default()); + + let mut data = [0u8; 1]; + unwrap!(i2c.blocking_write_read(HTS221_ADDRESS, &[WHOAMI], &mut data)); + + // HTS221 data sheet is here: https://www.st.com/resource/en/datasheet/hts221.pdf + // 7.1 WHO_AM_I command is x0F which expected response xBC. + info!("Whoami: 0x{:02x}", data[0]); + assert_eq!(0xBC, data[0]); +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/ltdc.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/ltdc.rs new file mode 100644 index 0000000000..266fe4ab95 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/ltdc.rs @@ -0,0 +1,461 @@ +#![no_std] +#![no_main] +#![macro_use] +#![allow(static_mut_refs)] + +/// This example was derived from examples\stm32h735\src\bin\ltdc.rs +/// It demonstrates the LTDC lcd display peripheral and was tested on an STM32U5G9J-DK2 demo board (embassy-stm32 feature "stm32u5g9zj" and probe-rs chip "STM32U5G9ZJTxQ") +/// +use bouncy_box::BouncyBox; +use defmt::{info, unwrap}; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::ltdc::{self, Ltdc, LtdcConfiguration, LtdcLayer, LtdcLayerConfig, PolarityActive, PolarityEdge}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::{Duration, Timer}; +use embedded_graphics::Pixel; +use embedded_graphics::draw_target::DrawTarget; +use embedded_graphics::geometry::{OriginDimensions, Point, Size}; +use embedded_graphics::image::Image; +use embedded_graphics::pixelcolor::Rgb888; +use embedded_graphics::pixelcolor::raw::RawU24; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::Rectangle; +use heapless::index_map::{Entry, FnvIndexMap}; +use panic_probe as _; +use tinybmp::Bmp; + +const DISPLAY_WIDTH: usize = 800; +const DISPLAY_HEIGHT: usize = 480; +const MY_TASK_POOL_SIZE: usize = 2; + +// the following two display buffers consume 261120 bytes that just about fits into axis ram found on the mcu +pub static mut FB1: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; +pub static mut FB2: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; + +bind_interrupts!(struct Irqs { + LTDC => ltdc::InterruptHandler; +}); + +const NUM_COLORS: usize = 256; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = rcc_setup::stm32u5g9zj_init(); + + // enable ICACHE + embassy_stm32::pac::ICACHE.cr().write(|w| { + w.set_en(true); + }); + + // blink the led on another task + let led = Output::new(p.PD2, Level::High, Speed::Low); + spawner.spawn(unwrap!(led_task(led))); + + // numbers from STM32U5G9J-DK2.ioc + const RK050HR18H_HSYNC: u16 = 5; // Horizontal synchronization + const RK050HR18H_HBP: u16 = 8; // Horizontal back porch + const RK050HR18H_HFP: u16 = 8; // Horizontal front porch + const RK050HR18H_VSYNC: u16 = 5; // Vertical synchronization + const RK050HR18H_VBP: u16 = 8; // Vertical back porch + const RK050HR18H_VFP: u16 = 8; // Vertical front porch + + // NOTE: all polarities have to be reversed with respect to the STM32U5G9J-DK2 CubeMX parametrization + let ltdc_config = LtdcConfiguration { + active_width: DISPLAY_WIDTH as _, + active_height: DISPLAY_HEIGHT as _, + h_back_porch: RK050HR18H_HBP, + h_front_porch: RK050HR18H_HFP, + v_back_porch: RK050HR18H_VBP, + v_front_porch: RK050HR18H_VFP, + h_sync: RK050HR18H_HSYNC, + v_sync: RK050HR18H_VSYNC, + h_sync_polarity: PolarityActive::ActiveHigh, + v_sync_polarity: PolarityActive::ActiveHigh, + data_enable_polarity: PolarityActive::ActiveHigh, + pixel_clock_polarity: PolarityEdge::RisingEdge, + }; + + info!("init ltdc"); + let mut ltdc_disp_ctrl = Output::new(p.PE4, Level::Low, Speed::High); + let mut ltdc_bl_ctrl = Output::new(p.PE6, Level::Low, Speed::High); + let mut ltdc = Ltdc::<_, ltdc::Rgb888>::new_with_pins( + p.LTDC, // PERIPHERAL + Irqs, // IRQS + p.PD3, // CLK + p.PE0, // HSYNC + p.PD13, // VSYNC + p.PD6, // DE + p.PB9, // B0 + p.PB2, // B1 + p.PD14, // B2 + p.PD15, // B3 + p.PD0, // B4 + p.PD1, // B5 + p.PE7, // B6 + p.PE8, // B7 + p.PC8, // G0 + p.PC9, // G1 + p.PE9, // G2 + p.PE10, // G3 + p.PE11, // G4 + p.PE12, // G5 + p.PE13, // G6 + p.PE14, // G7 + p.PC6, // R0 + p.PC7, // R1 + p.PE15, // R2 + p.PD8, // R3 + p.PD9, // R4 + p.PD10, // R5 + p.PD11, // R6 + p.PD12, // R7 + ); + ltdc.init(<dc_config); + ltdc_bl_ctrl.set_high(); + ltdc_disp_ctrl.set_high(); + + // we only need to draw on one layer for this example (not to be confused with the double buffer) + info!("enable bottom layer"); + let layer_config = LtdcLayerConfig { + pixel_format: ltdc::PixelFormat::L8, // 1 byte per pixel + layer: LtdcLayer::Layer1, + window_x0: 0, + window_x1: DISPLAY_WIDTH as _, + window_y0: 0, + window_y1: DISPLAY_HEIGHT as _, + }; + + let ferris_bmp: Bmp = Bmp::from_slice(include_bytes!("./ferris.bmp")).unwrap(); + let color_map = build_color_lookup_map(&ferris_bmp); + let clut = build_clut(&color_map); + + // enable the bottom layer with a 256 color lookup table + ltdc.init_layer(&layer_config, Some(&clut)); + + // Safety: the DoubleBuffer controls access to the statically allocated frame buffers + // and it is the only thing that mutates their content + let mut double_buffer = DoubleBuffer::new( + unsafe { FB1.as_mut() }, + unsafe { FB2.as_mut() }, + layer_config, + color_map, + ); + + // this allows us to perform some simple animation for every frame + let mut bouncy_box = BouncyBox::new( + ferris_bmp.bounding_box(), + Rectangle::new(Point::zero(), Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)), + 2, + ); + + loop { + // cpu intensive drawing to the buffer that is NOT currently being copied to the LCD screen + double_buffer.clear(); + let position = bouncy_box.next_point(); + let ferris = Image::new(&ferris_bmp, position); + unwrap!(ferris.draw(&mut double_buffer)); + + // perform async dma data transfer to the lcd screen + unwrap!(double_buffer.swap(&mut ltdc).await); + } +} + +/// builds the color look-up table from all unique colors found in the bitmap. This should be a 256 color indexed bitmap to work. +fn build_color_lookup_map(bmp: &Bmp) -> FnvIndexMap { + let mut color_map: FnvIndexMap = FnvIndexMap::new(); + let mut counter: u8 = 0; + + // add black to position 0 + color_map.insert(Rgb888::new(0, 0, 0).into_storage(), counter).unwrap(); + counter += 1; + + for Pixel(_point, color) in bmp.pixels() { + let raw = color.into_storage(); + if let Entry::Vacant(v) = color_map.entry(raw) { + v.insert(counter).expect("more than 256 colors detected"); + counter += 1; + } + } + color_map +} + +/// builds the color look-up table from the color map provided +fn build_clut(color_map: &FnvIndexMap) -> [ltdc::RgbColor; NUM_COLORS] { + let mut clut = [ltdc::RgbColor::default(); NUM_COLORS]; + for (color, index) in color_map.iter() { + let color = Rgb888::from(RawU24::new(*color)); + clut[*index as usize] = ltdc::RgbColor { + red: color.r(), + green: color.g(), + blue: color.b(), + }; + } + + clut +} + +#[embassy_executor::task(pool_size = MY_TASK_POOL_SIZE)] +async fn led_task(mut led: Output<'static>) { + let mut counter = 0; + loop { + info!("blink: {}", counter); + counter += 1; + + // on + led.set_low(); + Timer::after(Duration::from_millis(50)).await; + + // off + led.set_high(); + Timer::after(Duration::from_millis(450)).await; + } +} + +pub type TargetPixelType = u8; + +// A simple double buffer +pub struct DoubleBuffer { + buf0: &'static mut [TargetPixelType], + buf1: &'static mut [TargetPixelType], + is_buf0: bool, + layer_config: LtdcLayerConfig, + color_map: FnvIndexMap, +} + +impl DoubleBuffer { + pub fn new( + buf0: &'static mut [TargetPixelType], + buf1: &'static mut [TargetPixelType], + layer_config: LtdcLayerConfig, + color_map: FnvIndexMap, + ) -> Self { + Self { + buf0, + buf1, + is_buf0: true, + layer_config, + color_map, + } + } + + pub fn current(&mut self) -> (&FnvIndexMap, &mut [TargetPixelType]) { + if self.is_buf0 { + (&self.color_map, self.buf0) + } else { + (&self.color_map, self.buf1) + } + } + + pub async fn swap(&mut self, ltdc: &mut Ltdc<'_, T>) -> Result<(), ltdc::Error> { + let (_, buf) = self.current(); + let frame_buffer = buf.as_ptr(); + self.is_buf0 = !self.is_buf0; + ltdc.set_buffer(self.layer_config.layer, frame_buffer as *const _).await + } + + /// Clears the buffer + pub fn clear(&mut self) { + let (color_map, buf) = self.current(); + let black = Rgb888::new(0, 0, 0).into_storage(); + let color_index = color_map.get(&black).expect("no black found in the color map"); + + for a in buf.iter_mut() { + *a = *color_index; // solid black + } + } +} + +// Implement DrawTarget for +impl DrawTarget for DoubleBuffer { + type Color = Rgb888; + type Error = (); + + /// Draw a pixel + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + let size = self.size(); + let width = size.width as i32; + let height = size.height as i32; + let (color_map, buf) = self.current(); + + for pixel in pixels { + let Pixel(point, color) = pixel; + + if point.x >= 0 && point.y >= 0 && point.x < width && point.y < height { + let index = point.y * width + point.x; + let raw_color = color.into_storage(); + + match color_map.get(&raw_color) { + Some(x) => { + buf[index as usize] = *x; + } + None => panic!("color not found in color map: {}", raw_color), + }; + } else { + // Ignore invalid points + } + } + + Ok(()) + } +} + +impl OriginDimensions for DoubleBuffer { + /// Return the size of the display + fn size(&self) -> Size { + Size::new( + (self.layer_config.window_x1 - self.layer_config.window_x0) as _, + (self.layer_config.window_y1 - self.layer_config.window_y0) as _, + ) + } +} + +mod rcc_setup { + + use embassy_stm32::time::Hertz; + use embassy_stm32::{Config, Peripherals, rcc}; + + /// Sets up clocks for the stm32u5g9zj mcu + /// change this if you plan to use a different microcontroller + pub fn stm32u5g9zj_init() -> Peripherals { + // setup power and clocks for an STM32U5G9J-DK2 run from an external 16 Mhz external oscillator + let mut config = Config::default(); + config.rcc.hse = Some(rcc::Hse { + freq: Hertz(16_000_000), + mode: rcc::HseMode::Oscillator, + }); + config.rcc.pll1 = Some(rcc::Pll { + source: rcc::PllSource::Hse, + prediv: rcc::PllPreDiv::Div1, + mul: rcc::PllMul::Mul10, + divp: None, + divq: None, + divr: Some(rcc::PllDiv::Div1), + }); + config.rcc.sys = rcc::Sysclk::Pll1R; // 160 Mhz + config.rcc.pll3 = Some(rcc::Pll { + source: rcc::PllSource::Hse, + prediv: rcc::PllPreDiv::Div4, // PLL_M + mul: rcc::PllMul::Mul125, // PLL_N + divp: None, + divq: None, + divr: Some(rcc::PllDiv::Div20), + }); + config.rcc.mux.ltdcsel = rcc::mux::Ltdcsel::Pll3R; // 25 MHz + embassy_stm32::init(config) + } +} + +mod bouncy_box { + use embedded_graphics::geometry::Point; + use embedded_graphics::primitives::Rectangle; + + enum Direction { + DownLeft, + DownRight, + UpLeft, + UpRight, + } + + pub struct BouncyBox { + direction: Direction, + child_rect: Rectangle, + parent_rect: Rectangle, + current_point: Point, + move_by: usize, + } + + // This calculates the coordinates of a chile rectangle bounced around inside a parent bounded box + impl BouncyBox { + pub fn new(child_rect: Rectangle, parent_rect: Rectangle, move_by: usize) -> Self { + let center_box = parent_rect.center(); + let center_img = child_rect.center(); + let current_point = Point::new(center_box.x - center_img.x / 2, center_box.y - center_img.y / 2); + Self { + direction: Direction::DownRight, + child_rect, + parent_rect, + current_point, + move_by, + } + } + + pub fn next_point(&mut self) -> Point { + let direction = &self.direction; + let img_height = self.child_rect.size.height as i32; + let box_height = self.parent_rect.size.height as i32; + let img_width = self.child_rect.size.width as i32; + let box_width = self.parent_rect.size.width as i32; + let move_by = self.move_by as i32; + + match direction { + Direction::DownLeft => { + self.current_point.x -= move_by; + self.current_point.y += move_by; + + let x_out_of_bounds = self.current_point.x < 0; + let y_out_of_bounds = (self.current_point.y + img_height) > box_height; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpRight + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::DownRight + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpLeft + } + } + Direction::DownRight => { + self.current_point.x += move_by; + self.current_point.y += move_by; + + let x_out_of_bounds = (self.current_point.x + img_width) > box_width; + let y_out_of_bounds = (self.current_point.y + img_height) > box_height; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpLeft + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::DownLeft + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpRight + } + } + Direction::UpLeft => { + self.current_point.x -= move_by; + self.current_point.y -= move_by; + + let x_out_of_bounds = self.current_point.x < 0; + let y_out_of_bounds = self.current_point.y < 0; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownRight + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::UpRight + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownLeft + } + } + Direction::UpRight => { + self.current_point.x += move_by; + self.current_point.y -= move_by; + + let x_out_of_bounds = (self.current_point.x + img_width) > box_width; + let y_out_of_bounds = self.current_point.y < 0; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownLeft + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::UpLeft + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownRight + } + } + } + + self.current_point + } + } +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/pdm.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/pdm.rs new file mode 100644 index 0000000000..3ba207a0c4 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/pdm.rs @@ -0,0 +1,104 @@ +//! PDM microphone capture on STM32U5 using MDF filter 0. +//! +//! Tested on NUCLEO-U545RE-Q / NUCLEO-U575ZI-Q class boards (see `Cargo.toml` chip feature). +//! +//! # Hardware +//! +//! Connect a PDM microphone (e.g. MP34DT05) to: +//! - **CLK** → `PE9` (MDF1 CCK0) +//! - **DATA** → `PE10` (MDF1 SDI0) +//! - **3V3** and **GND** as usual +//! +//! The MDF kernel clock must be configured in RCC before use — same as other U5 peripherals +//! with a dedicated clock mux (DAC, FDCAN, SAI, etc.). This example routes MDF1 from HCLK1. +//! +//! # Output +//! +//! Decimated PCM samples are read via DMA. The example prints an approximate RMS level over +//! defmt once per second. With no microphone attached the level stays near zero. + +#![no_std] +#![no_main] + +use defmt::*; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::mdf::{self, Config, Flt0, Mdf, samples_from_dma_words}; +use embassy_stm32::rcc::{self, mux}; +use embassy_stm32::{Config as StmConfig, bind_interrupts, dma, peripherals}; +use embassy_time::Timer; +use micromath::F32Ext; +use panic_probe as _; + +/// Target PDM bit clock. Common for many MEMS PDM mics (1–3.2 MHz). +const PDM_CCK_HZ: u32 = 2_000_000; + +const RAW_BUF_LEN: usize = 256; + +bind_interrupts!(struct Irqs { + MDF1_FLT0 => mdf::FilterInterruptHandler; + GPDMA1_CHANNEL0 => dma::InterruptHandler; +}); + +/// Build MDF config for filter 0 / SITF 0 at the given kernel clock. +fn mdf_config(kernel_hz: u32) -> Config { + let mut config = Config::for_filter(0); + // Processing clock = kernel / (procdiv + 1); CCK0 uses that clock with cckdiv = 1. + config.clock.procdiv = kernel_hz.div_ceil(PDM_CCK_HZ).saturating_sub(1).min(255) as u8; + config +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut stm_config = StmConfig::default(); + // Peripherals with kernel-clock muxes are configured in application RCC setup, not in the driver. + stm_config.rcc.mux.mdf1sel = mux::Mdfsel::Hclk1; + + let p = embassy_stm32::init(stm_config); + info!("PDM example started"); + + let kernel_hz = rcc::frequency::().0; + let config = mdf_config(kernel_hz); + + let mut dma_buf = [0u32; RAW_BUF_LEN]; + let mut raw = [0u32; RAW_BUF_LEN]; + let mut samples = [0i32; RAW_BUF_LEN]; + + let mut mdf = Mdf::new( + p.MDF1, + Irqs, + config, + p.PE9, // CCK0 + p.PE10, // SDI0 + p.GPDMA1_CH0, + &mut dma_buf, + ); + + info!( + "MDF kernel clock: {} Hz, procdiv: {}, target PDM clock: {} Hz", + kernel_hz, config.clock.procdiv, PDM_CCK_HZ + ); + + mdf.start(); + + loop { + match mdf.read_raw(&mut raw).await { + Ok(()) => { + samples_from_dma_words(&raw, &mut samples); + let rms = rms(&samples); + info!("PCM RMS (approx): {}", rms as u32); + } + Err(e) => error!("MDF read error: {:?}", e), + } + + Timer::after_secs(1).await; + } +} + +fn rms(samples: &[i32]) -> f32 { + if samples.is_empty() { + return 0.0; + } + let sum_sq: f32 = samples.iter().map(|s| (*s as f32) * (*s as f32)).sum(); + (sum_sq / samples.len() as f32).sqrt() +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/rng.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/rng.rs new file mode 100644 index 0000000000..283721b134 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/rng.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::rng::Rng; +use embassy_stm32::{bind_interrupts, peripherals, rng}; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + RNG => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + info!("Hello World!"); + + let mut rng = Rng::new(p.RNG, Irqs); + + let mut buf = [0u8; 16]; + unwrap!(rng.async_fill_bytes(&mut buf).await); + info!("random bytes: {:02x}", buf); +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/spi.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/spi.rs new file mode 100644 index 0000000000..da2e8dea3b --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/spi.rs @@ -0,0 +1,59 @@ +//! This example uses the BMP390 barometric pressure sensor, for simplicity we only read the chip ID +//! To read the chip ID of the BMP390, send a read request to register 0x00, it should return 0x60 +#![no_std] +#![no_main] + +use defmt::*; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::spi::{Config, Spi}; +use embassy_stm32::time::Hertz; +use panic_probe as _; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Device has started!"); + + // Initialize peripherals + let p = embassy_stm32::init(Default::default()); + + // Mutable spi configuration with default attributes + let mut spi_config = Config::default(); + // The exact frequency does not matter for this example, change this as needed for your hardware + spi_config.frequency = Hertz(1_000_000); + + // PIN naming + let sck = p.PA5; + let mosi = p.PA7; + let miso = p.PA6; + let mut spi = Spi::new_blocking(p.SPI1, sck, mosi, miso, spi_config); + let cs = p.PC9; + + let mut chip_select = Output::new(cs, Level::High, Speed::VeryHigh); + + loop { + // BMP390 Chip ID read buffer: + // Byte 0: 0x80 (Read Register 0x00) + // Byte 1: 0x00 (Dummy Byte) + // Byte 2: 0x00 (Extra Dummy to receive the answer) + let mut buf: [u8; 3] = [0x80, 0x00, 0x00]; + + // Wake up sensor + chip_select.set_low(); + + // Error logging + if let Err(e) = spi.blocking_transfer_in_place(&mut buf) { + error!("SPI Error: {:?}", e); + } + + // Put sensor to sleep + chip_select.set_high(); + + // Nice formatting + info!("Raw buffer: {=[u8]:x} | BMP390 Chip ID: {=u8:#04x}", buf, buf[2]); + + // Delay so we wont flood the console + cortex_m::asm::delay(8_000_000); + } +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/spi_dma.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/spi_dma.rs new file mode 100644 index 0000000000..e1468dba29 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/spi_dma.rs @@ -0,0 +1,69 @@ +//! This example uses the BMP390 barometric pressure sensor, for simplicity we only read the chip ID +//! To read the chip ID of the BMP390, send a read request to register 0x00, it should return 0x60 +#![no_std] +#![no_main] + +use defmt::*; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::spi::{Config, Spi}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + GPDMA1_CHANNEL0 => embassy_stm32::dma::InterruptHandler; + GPDMA1_CHANNEL1 => embassy_stm32::dma::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Device started"); + // Initialize peripherals + let p = embassy_stm32::init(Default::default()); + + // Configure spi and its frequency + let mut spi_conf = Config::default(); + + // The exact frequency does not matter for this example, change this as needed for your hardware + spi_conf.frequency = Hertz(1_000_000); + + // Naming the pins for clarity + let sck = p.PA5; + let miso = p.PA6; + let mosi = p.PA7; + let cs = p.PC9; + let dma_transfer = p.GPDMA1_CH0; + let dma_receive = p.GPDMA1_CH1; + + let mut spi = Spi::new(p.SPI1, sck, mosi, miso, dma_transfer, dma_receive, Irqs, spi_conf); + let mut chip_select = Output::new(cs, Level::High, Speed::VeryHigh); + + info!("Starting sensor read!"); + loop { + // BMP390 Chip ID read buffer: + // Byte 0: 0x80 (Read Register 0x00) + // Byte 1: 0x00 (Dummy Byte) + // Byte 2: 0x00 (Extra Dummy to receive the answer) + let tx_buf: [u8; 3] = [0x80, 0x00, 0x00]; + let mut rx_buf: [u8; 3] = [0x00; 3]; + + // Wake up sensor + chip_select.set_low(); + + // .await puts the CPU to sleep while the DMA handles the transfer + if let Err(e) = spi.transfer(&mut rx_buf, &tx_buf).await { + error!("SPI Error: {:?}", e); + } + + // Put sensor to sleep + chip_select.set_high(); + + // Nice formatting for console output + info!("Raw buffer: {=[u8]:x} | BMP390 Chip ID: {=u8:#04x}", rx_buf, rx_buf[2]); + + Timer::after_secs(1).await; + } +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/test_all.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/test_all.rs new file mode 100644 index 0000000000..71dddd8d11 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/test_all.rs @@ -0,0 +1,203 @@ +#![no_std] +#![no_main] + +use core::fmt::Write as _; +use core::sync::atomic::{AtomicU8, Ordering}; +use defmt::*; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, adc4}; +use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_stm32::i2c::I2c; +use embassy_stm32::usart::{Config, Uart}; +use embassy_time::Timer; +use panic_probe as _; + +use ssd1306::{prelude::*, size::DisplaySize128x64, rotation::DisplayRotation, I2CDisplayInterface, Ssd1306}; +use embedded_graphics::{ + mono_font::{ascii::FONT_6X10, MonoTextStyleBuilder}, + pixelcolor::BinaryColor, + prelude::*, + text::{Baseline, Text}, +}; + +const NUME: [&str; 13] = ["C","C#","D","D#","E","F","F#","G","G#","A","A#","B","C"]; + +const LEVELS: u8 = 8; // trepte de intensitate (rezolutie PWM) +static DUTY_R: AtomicU8 = AtomicU8::new(0); +static DUTY_G: AtomicU8 = AtomicU8::new(0); +static DUTY_B: AtomicU8 = AtomicU8::new(0); + +// Task de PWM software: controleaza intensitatea celor 3 culori. +#[embassy_executor::task] +async fn led_task(mut r: Output<'static>, mut g: Output<'static>, mut b: Output<'static>) { + loop { + let dr = DUTY_R.load(Ordering::Relaxed); + let dg = DUTY_G.load(Ordering::Relaxed); + let db = DUTY_B.load(Ordering::Relaxed); + for step in 0..LEVELS { + if step < dr { r.set_high(); } else { r.set_low(); } + if step < dg { g.set_high(); } else { g.set_low(); } + if step < db { b.set_high(); } else { b.set_low(); } + Timer::after_micros(700).await; + } + } +} + +fn detect_acord(pressed: &[u8]) -> Option<(&'static str, u8, u8, u8)> { + let mut pc = [false; 12]; + for &k in pressed { pc[(k % 12) as usize] = true; } + let mut cls = heapless::Vec::::new(); + for i in 0..12 { if pc[i] { let _ = cls.push(i as u8); } } + if cls.len() != 3 { return None; } + let root = cls[0]; + let (i1, i2) = (cls[1] - root, cls[2] - root); + let nume = match (i1, i2) { + (4, 7) => "Major", + (3, 7) => "Minor", + (3, 6) => "Diminished", + (4, 8) => "Augmented", + _ => return None, + }; + Some((nume, root, i1, i2)) +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) -> ! { + let p = embassy_stm32::init(Default::default()); + info!("===firmware v3 ==="); + + let i2c = I2c::new_blocking(p.I2C1, p.PB6, p.PB7, Default::default()); + let mut display = Ssd1306::new(I2CDisplayInterface::new(i2c), DisplaySize128x64, DisplayRotation::Rotate0) + .into_buffered_graphics_mode(); + display.init().unwrap(); + let style = MonoTextStyleBuilder::new().font(&FONT_6X10).text_color(BinaryColor::On).build(); + + // RGB -> preluate de led_task (R=PB5, G=PB3, B=PB4) + let led_r = Output::new(p.PB5, Level::Low, Speed::Low); + let led_g = Output::new(p.PB3, Level::Low, Speed::Low); + let led_b = Output::new(p.PB4, Level::Low, Speed::Low); + spawner.spawn(led_task(led_r, led_g, led_b).unwrap()); + + // ADC4: volum=PA4, octava=PA7, microfon=PA6 + let mut adc = Adc::new_adc4(p.ADC4); + adc.set_resolution_adc4(adc4::Resolution::Bits12); + let mut pin_vol = p.PA4; + let mut pin_oct = p.PA7; + let mut pin_mic = p.PA6; + + let mut ucfg = Config::default(); + ucfg.baudrate = 115200; + let mut usart = Uart::new_blocking(p.USART1, p.PA10, p.PA9, ucfg).unwrap(); + + let buttons = [ + Input::new(p.PC0, Pull::Up), // 0 C + Input::new(p.PB1, Pull::Up), // 1 C# + Input::new(p.PC6, Pull::Up), // 2 D + Input::new(p.PC7, Pull::Up), // 3 D# + Input::new(p.PC9, Pull::Up), // 4 E + Input::new(p.PC11, Pull::Up), // 5 F + Input::new(p.PA8, Pull::Up), // 6 F# + Input::new(p.PC1, Pull::Up), // 7 G + Input::new(p.PC3, Pull::Up), // 8 G# + Input::new(p.PC8, Pull::Up), // 9 A + Input::new(p.PC10, Pull::Up), // 10 A# + Input::new(p.PC12, Pull::Up), // 11 B + ]; + + + let mut committed: u16 = 0xFFFF; + let mut prev_read: u16 = 0; + // culoarea curenta (implicit alb, ca microfonul sa pulseze mereu) + let (mut cr, mut cg, mut cb) = (true, true, true); + + loop { + let mut pressed = heapless::Vec::::new(); + let mut mask: u16 = 0; + for (i, b) in buttons.iter().enumerate() { + if b.is_low() { let _ = pressed.push(i as u8); mask |= 1 << i; } + } + + let raw_vol = adc.blocking_read(&mut pin_vol, adc4::SampleTime::Cycles15); + let raw_oct = adc.blocking_read(&mut pin_oct, adc4::SampleTime::Cycles15); + let volum = (raw_vol as u32 * 100 / 4095) as u8; + let octava = 2 + (raw_oct as u32 * 5 / 4096).min(4) as u8; + + // --- MICROFON -> intensitate --- + let mut mn = u16::MAX; + let mut mx = 0u16; + for _ in 0..64 { + let v = adc.blocking_read(&mut pin_mic, adc4::SampleTime::Cycles15); + if v < mn { mn = v; } + if v > mx { mx = v; } + } + let amplitude = mx - mn; + // mapare amplitudine -> 1..LEVELS (SCALE il ajustezi dupa microfonul tau) + const SCALE: u32 = 50; //te poti juca cu valoarea asta ca sa ajustezi sensibilitatea microfonului + let bright = ((amplitude as u32 / SCALE).min(LEVELS as u32) as u8).max(1); + + // aplica culoarea x intensitate + DUTY_R.store(if cr { bright } else { 0 }, Ordering::Relaxed); + DUTY_G.store(if cg { bright } else { 0 }, Ordering::Relaxed); + DUTY_B.store(if cb { bright } else { 0 }, Ordering::Relaxed); + + // --- actioneaza doar la schimbarea tastelor (debounce) --- + if mask == prev_read && mask != committed { + committed = mask; + + let mut l1 = heapless::String::<24>::new(); + let mut l2 = heapless::String::<24>::new(); + let mut json = heapless::String::<96>::new(); + // implicit alb + cr = true; cg = true; cb = true; + + if pressed.len() >= 3 { + if let Some((nume, root, i1, i2)) = detect_acord(&pressed) { + let rn = NUME[(root % 12) as usize]; + let n0 = 60 + root; + let (n1, n2) = (n0 + i1, n0 + i2); + let _ = core::write!(l1, "{} {}", rn, nume); + let _ = core::write!(l2, "Vol:{}", volum); + let _ = core::write!(json, + "{{\"mode\":\"chord\",\"chord\":\"{} {}\",\"notes\":[{},{},{}],\"volume\":{}}}\r\n", + rn, nume, n0, n1, n2, volum); + // culoare dupa acord + match nume { + "Major" => { cr=false; cg=true; cb=false; } + "Minor" => { cr=false; cg=false; cb=true; } + "Diminished" => { cr=true; cg=false; cb=false; } + "Augmented" => { cr=true; cg=true; cb=false; } + _ => {} + } + } else { + let _ = core::write!(l1, "Acord ?"); + let _ = core::write!(l2, "Vol:{}", volum); + let _ = core::write!(json, "{{\"mode\":\"off\"}}\r\n"); + } + } else if pressed.len() == 1 { + let idx = pressed[0]; + let note = 12 * (octava + 1) + idx; + let _ = core::write!(l1, "Nota:{}{}", NUME[(idx % 12) as usize], octava); + let _ = core::write!(l2, "Vol:{} Oct:{}", volum, octava); + let _ = core::write!(json, + "{{\"mode\":\"note\",\"note\":{},\"octave\":{},\"volume\":{}}}\r\n", + note, octava, volum); + } else { + let _ = core::write!(l1, "-"); + let _ = core::write!(l2, "Vol:{} Oct:{}", volum, octava); + let _ = core::write!(json, "{{\"mode\":\"off\"}}\r\n"); + } + + display.clear(BinaryColor::Off).unwrap(); + Text::with_baseline(&l1, Point::new(0, 0), style, Baseline::Top).draw(&mut display).unwrap(); + Text::with_baseline(&l2, Point::new(0, 20), style, Baseline::Top).draw(&mut display).unwrap(); + display.flush().unwrap(); + + let _ = usart.blocking_write(json.as_bytes()); + info!("{}", json.as_str()); + } + + prev_read = mask; + Timer::after_millis(10).await; + } +} \ No newline at end of file diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/tsc.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/tsc.rs new file mode 100644 index 0000000000..eac9912935 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/tsc.rs @@ -0,0 +1,85 @@ +#![no_std] +#![no_main] + +use defmt::*; +use defmt_rtt as _; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + TSC => InterruptHandler; +}); + +#[cortex_m_rt::exception] +unsafe fn HardFault(_: &cortex_m_rt::ExceptionFrame) -> ! { + cortex_m::peripheral::SCB::sys_reset(); +} + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let config = tsc::Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_2, + ct_pulse_low_length: ChargeTransferPulseCycle::_2, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_4, + max_count_value: MaxCount::_8191, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let mut g1: PinGroupWithRoles = PinGroupWithRoles::default(); + g1.set_io2::(context.PB13); + g1.set_io3::(context.PB14); + + let mut g2: PinGroupWithRoles = PinGroupWithRoles::default(); + g2.set_io1::(context.PB4); + let sensor0 = g2.set_io2(context.PB5); + + let mut g7: PinGroupWithRoles = PinGroupWithRoles::default(); + g7.set_io2::(context.PE3); + let sensor1 = g7.set_io3(context.PE4); + + let pin_groups: PinGroups = PinGroups { + g1: Some(g1.pin_group), + g2: Some(g2.pin_group), + g7: Some(g7.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap(); + + let acquisition_bank = touch_controller.create_acquisition_bank(AcquisitionBankPins { + g2_pin: Some(sensor0), + g7_pin: Some(sensor1), + ..Default::default() + }); + + touch_controller.set_active_channels_bank(&acquisition_bank); + + info!("Starting touch_controller interface"); + loop { + touch_controller.start(); + touch_controller.pend_for_acquisition().await; + touch_controller.discharge_io(true); + Timer::after_millis(1).await; + + let status = touch_controller.get_acquisition_bank_status(&acquisition_bank); + + if status.all_complete() { + let read_values = touch_controller.get_acquisition_bank_values(&acquisition_bank); + let group2_reading = read_values.get_group_reading(Group::Two).unwrap(); + let group7_reading = read_values.get_group_reading(Group::Seven).unwrap(); + info!("group 2 value: {}", group2_reading.sensor_value); + info!("group 7 value: {}", group7_reading.sensor_value); + } + } +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/usb_hs_serial.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/usb_hs_serial.rs new file mode 100644 index 0000000000..f2b73f29eb --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/usb_hs_serial.rs @@ -0,0 +1,123 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{Config, bind_interrupts, peripherals, usb}; +use embassy_usb::Builder; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + OTG_HS => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + use embassy_stm32::time::Hertz; + config.rcc.hse = Some(Hse { + freq: Hertz(16_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::Hse, + prediv: PllPreDiv::Div2, // HSE / 2 = 8MHz + mul: PllMul::Mul60, // 8MHz * 60 = 480MHz + divr: Some(PllDiv::Div3), // 480MHz / 3 = 160MHz (sys_ck) + divq: Some(PllDiv::Div10), // 480MHz / 10 = 48MHz (USB) + divp: Some(PllDiv::Div15), // 480MHz / 15 = 32MHz (USBOTG) + }); + config.rcc.mux.otghssel = mux::Otghssel::Pll1P; + config.rcc.voltage_range = VoltageScale::Range1; + config.rcc.sys = Sysclk::Pll1R; + } + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 1024]; + let mut config = embassy_stm32::usb::Config::default(); + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + let driver = Driver::new_hs(p.USB_OTG_HS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + // High-speed bulk endpoints must have a max packet size of 512 bytes. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 512); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 512]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/usb_serial.rs b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/usb_serial.rs new file mode 100644 index 0000000000..c366696b56 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi/src/bin/usb_serial.rs @@ -0,0 +1,119 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{Config, bind_interrupts, peripherals, usb}; +use embassy_usb::Builder; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + OTG_HS => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::Hsi, // 16 MHz + prediv: PllPreDiv::Div1, + mul: PllMul::Mul10, + divp: None, + divq: None, + divr: Some(PllDiv::Div1), // 160 MHz + }); + config.rcc.sys = Sysclk::Pll1R; + config.rcc.voltage_range = VoltageScale::Range1; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.mux.iclksel = mux::Iclksel::Hsi48; // USB uses ICLK + } + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb::Config::default(); + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + let driver = Driver::new_hs(p.USB_OTG_HS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi_controller.kicad.svg b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi_controller.kicad.svg new file mode 100644 index 0000000000..c01078d514 --- /dev/null +++ b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/midi_controller.kicad.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/workprogress.webp.webp b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/workprogress.webp.webp new file mode 100644 index 0000000000..b4217d66c1 Binary files /dev/null and b/website/versioned_docs/version-fils_en/project/2026/delia_andreea.racu/workprogress.webp.webp differ