Skip to content

Add FMAC implementation #214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
29 changes: 24 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ stm32g4 = { version = "0.16.0", features = ["atomics"] }
paste = "1.0"
fugit = "0.3.7"
stm32-usbd = { version = "0.7.0", optional = true }
fixed = { version = "1.28.0", optional = true }
fixed = { version = "1.28.0" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why make this no longer optional?

Copy link
Contributor Author

@boondocklabs boondocklabs Jun 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually originated from DualDac PR, which wasn't feature gated and depends on fixed::types - not strictly necessary for this PR, but if DualDac is merged as-is, it wouldn't be optional. Could either feature gate dualdac or just leave this as is

embedded-io = "0.6"
stm32-hrtim = { version = "0.1.0", optional = true }

Expand Down Expand Up @@ -82,9 +82,23 @@ usb = ["dep:stm32-usbd"]
stm32g431 = ["stm32g4/stm32g431", "cat2"]
stm32g441 = ["stm32g4/stm32g441", "cat2"]
stm32g473 = ["stm32g4/stm32g473", "cat3", "adc3", "adc4", "adc5"]
stm32g474 = ["stm32g4/stm32g474", "cat3", "adc3", "adc4", "adc5", "stm32-hrtim/stm32g474"]
stm32g474 = [
"stm32g4/stm32g474",
"cat3",
"adc3",
"adc4",
"adc5",
"stm32-hrtim/stm32g474",
]
stm32g483 = ["stm32g4/stm32g483", "cat3", "adc3", "adc4", "adc5"]
stm32g484 = ["stm32g4/stm32g484", "cat3", "adc3", "adc4", "adc5", "stm32-hrtim/stm32g484"]
stm32g484 = [
"stm32g4/stm32g484",
"cat3",
"adc3",
"adc4",
"adc5",
"stm32-hrtim/stm32g484",
]
stm32g491 = ["stm32g4/stm32g491", "cat4", "adc3"]
stm32g4a1 = ["stm32g4/stm32g4a1", "cat4", "adc3"]

Expand All @@ -106,9 +120,10 @@ defmt = [
"embedded-hal/defmt-03",
"embedded-io/defmt-03",
"embedded-test/defmt",
"stm32-hrtim?/defmt"
"stm32-hrtim?/defmt",
]
cordic = ["dep:fixed"]
cordic = []
fmac = []
Comment on lines +125 to +126
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cordic = []
fmac = []
cordic = ["dep:fixed"]
fmac = ["dep:fixed"]

adc3 = []
adc4 = []
adc5 = []
Expand Down Expand Up @@ -174,6 +189,10 @@ required-features = ["usb"]
name = "cordic"
required-features = ["cordic"]

[[example]]
name = "fmac"
required-features = ["fmac"]

[[example]]
name = "hrtim-adc-trigger"
required-features = ["hrtim"]
Expand Down
2 changes: 2 additions & 0 deletions Embed.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[default.rtt]
enabled = true
15 changes: 10 additions & 5 deletions examples/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ use stm32g4xx_hal::{
//delay::{DelayExt, SYSTDelayExt},
gpio::{self, ExtiPin, GpioExt, Input, SignalEdge},
rcc::RccExt,
stm32,
stm32::{interrupt, Interrupt},
stm32::{self, interrupt, Interrupt},
syscfg::SysCfgExt,
};

use core::cell::RefCell;
use core::sync::atomic::{AtomicBool, Ordering};
use cortex_m::{asm::wfi, interrupt::Mutex};
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry;

type ButtonPin = gpio::PC13<Input>;
Expand Down Expand Up @@ -52,8 +51,14 @@ fn main() -> ! {
utils::logger::init();

let mut dp = stm32::Peripherals::take().expect("cannot take peripherals");

let mut rcc = dp.RCC.constrain();
let mut syscfg = dp.SYSCFG.constrain();

// Workaround for RTT when using wfi instruction
// Enable an AHB peripheral clock for debug probe with wfi
rcc.ahb1enr().modify(|_, w| w.dma1en().set_bit());

let mut syscfg = dp.SYSCFG.constrain(&mut rcc);

println!("Led Init");
// Configure PA5 pin to blink LED
Expand All @@ -80,7 +85,7 @@ fn main() -> ! {

println!("Start Loop");
loop {
wfi();
cortex_m::asm::wfi();
println!("Check");

if G_LED_ON.load(Ordering::Relaxed) {
Expand Down
97 changes: 97 additions & 0 deletions examples/fmac.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#![deny(warnings)]
#![deny(unsafe_code)]
#![no_main]
#![no_std]

use hal::prelude::*;
use hal::stm32;
use stm32g4xx_hal as hal;

use cortex_m_rt::entry;

#[macro_use]
mod utils;

use utils::logger::info;

use stm32g4xx_hal::fmac::{
buffer::{BufferLayout, Watermark},
function::{Gain, IIR},
Buffer, FmacExt,
};

use fixed::types::I1F15;

#[entry]
fn main() -> ! {
utils::logger::init();

info!("start");
let dp = stm32::Peripherals::take().expect("cannot take peripherals");
let mut rcc = dp.RCC.constrain();

// The FMAC has an internal memory area of 256x16bit words that must be allocated to the
// three different buffers named X1 (input buffer), X2 (coefficients), and Y (output buffer).
//
// The BufferLayout struct takes three generic consts and will calculate the base offsets at compile time,
// which must be passed as a generic parameter to the constrain method of the FMAC instance.
//
// Create an FMAC instance with a memory layout of 32 inputs, 2 coefficients, and 1 output buffer words
//
// For IIR filters, RM0440 indicates X2 coeffecient buffer should be 2N + 2M where
// N is the number of feedforward coefficients and M is the number of feedback coefficients.
//
// Following constrain(), the buffer layout has been applied to the peripheral and cannot be changed.
let mut fmac = dp
.FMAC
.constrain::<BufferLayout<32, 4, 1>>(&mut rcc)
.reset();

// Configure an IIR filter with 1 feedforward, and 1 feedback coefficient,
// with both coefficients set to 1.0.
//
// This is equivalent to a multiply accumulate operation
// Y[n] = ∑ X1[n] * X2[0] + Y[n-1] * X2[1]
//
// The inputs will be set to ~0.01 (to nearest fixed point representation), and the output will increment
// by 0.01 into Y for each input sample, and the final read of Y should be about 0.319
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it would make sense to add something like this as a test in here or here?


// Fill the input buffer with 0.01
fmac.preload_buffer(Buffer::X1, |_index| I1F15::from_num(0.01));

// Fill the coefficients with I1F15::MAX (max value representable by I1F15, ~1.0)
fmac.preload_buffer(Buffer::X2, |_index| I1F15::MAX);
fmac.preload_buffer(Buffer::Y, |_index| I1F15::ZERO);

// Watermarks can be set on the X1 and Y buffers
// to control when the input empty and output full
// flags are asserted to cause early interrupts or DMA
// to avoid underflow or overflow conditions.
fmac.set_watermark(Buffer::X1, Watermark::Threshold1);
fmac.set_watermark(Buffer::Y, Watermark::Threshold1);

// Select the IIR function, specifying the number of
// feedforward and feedback coefficients, and the gain
fmac.select_function(IIR {
feedforward_coeffs: 1,
feedback_coeffs: 1,
gain: Gain::ZeroDB,
});

let fmac = fmac.start();

info!("Input buffer full: {}", fmac.is_input_full());

info!("Waiting for result");
while !fmac.is_result_available() {}

info!("Reading results");

let mut count = 0;
loop {
while let Some(output) = fmac.read() {
count += 1;
info!("Output {}: {}", count, output.to_num::<f32>());
}
}
}
Loading
Loading