From f9dcb07de5a549f6a3490041e9a5f177315bbec2 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 2 Jul 2023 00:30:17 +0200 Subject: [PATCH] Ported basic things over from STM32f1043 version --- .cargo/config.toml | 21 +++++ .gitignore | 2 + Cargo.toml | 82 +++++++++++++++++++ memory.x | 11 +++ src/filters.rs | 98 ++++++++++++++++++++++ src/main.rs | 193 +++++++++++++++++++++++++++++++++++++++++++ src/si5153.rs | 199 +++++++++++++++++++++++++++++++++++++++++++++ ssb_filter.py | 37 +++++++++ 8 files changed, 643 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 memory.x create mode 100644 src/filters.rs create mode 100644 src/main.rs create mode 100644 src/si5153.rs create mode 100644 ssb_filter.py diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..d190172 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,21 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# TODO(2) replace `$CHIP` with your chip's name (see `probe-run --list-chips` output) +runner = "probe-run --chip STM32F401CCU6" +rustflags = [ + "-C", "linker=flip-link", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x + # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 + "-C", "link-arg=--nmagic", +] + +[build] +target = "thumbv7em-none-eabihf" + +[alias] +rb = "run --bin" +rrb = "run --release --bin" + +[env] +DEFMT_LOG = "debug" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f86ab23 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,82 @@ +[package] +authors = ["Sebastian "] +name = "fartrx" +edition = "2018" +version = "0.1.0" + +[dependencies] +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"]} +cortex-m-rt = "0.7.2" +cortex-m-rtic = "1.1.3" +defmt = "0.3.2" +defmt-rtt = "0.4" +panic-probe = { version = "0.3.0", features = ["print-defmt"] } +stm32f4xx-hal = { version = "0.16.2", features = ["stm32f401"] } +embedded-hal = {version = "0.2.3"} +nb = "1.0.0" +arrayvec = {version = "0.7.0", default-features = false} +systick-monotonic = "1.0.0" +num-traits = { version = "0.2", default-features = false, features = ["libm"] } +num = {version = "0.4", default-features = false} + + +[features] +# set logging levels here +default = [ + "defmt-default", + # "dependency-a/defmt-trace", +] + +# do NOT modify these features +defmt-default = [] +defmt-trace = [] +defmt-debug = [] +defmt-info = [] +defmt-warn = [] +defmt-error = [] + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 'z' # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# uncomment this to switch from the crates.io version of defmt to its git version +# check app-template's README for instructions +# [patch.crates-io] +# defmt = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" } +# defmt-rtt = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" } +# defmt-test = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" } +# panic-probe = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" } diff --git a/memory.x b/memory.x new file mode 100644 index 0000000..dfe3ff3 --- /dev/null +++ b/memory.x @@ -0,0 +1,11 @@ +MEMORY +{ + /* NOTE K = KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x08000000, LENGTH = 256K + RAM : ORIGIN = 0x20000000, LENGTH = 64K +} + +/* This is where the call stack will be allocated. */ +/* The stack is of the full descending type. */ +/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */ +_stack_start = ORIGIN(RAM) + LENGTH(RAM); \ No newline at end of file diff --git a/src/filters.rs b/src/filters.rs new file mode 100644 index 0000000..0c673b7 --- /dev/null +++ b/src/filters.rs @@ -0,0 +1,98 @@ +use num::complex::Complex; + +pub struct FirFilter { + state: [Complex; LEN], + coeffs: [Complex; LEN], + pos: usize, +} + +impl FirFilter { + pub fn new(coeffs: [Complex; LEN]) -> FirFilter { + FirFilter { + state: [Complex::new(0.0, 0.0); LEN], + coeffs: coeffs, + pos: 0, + } + } + + pub fn compute(&mut self, sample: Complex) -> Complex { + self.state[self.pos] = sample; + self.pos = (self.pos + 1) % LEN; + + let mut result = Complex::::new(0.0, 0.0); + + for i in 0..LEN { + result += self.state[(self.pos + i) % LEN] * self.coeffs[i]; + } + + result + } +} + +pub fn usb_firfilter() -> FirFilter<63> { + FirFilter::new([ + Complex::new(-7.76328407e-03, 0.00000000e+00), + Complex::new(-1.76283137e-04, -4.92678363e-04), + Complex::new(-6.25577634e-03, 5.13398296e-03), + Complex::new(-6.39279452e-03, -3.83169357e-03), + Complex::new(-5.03930054e-04, 2.53342746e-03), + Complex::new(-1.03144816e-02, 1.53000882e-03), + Complex::new(-2.84091140e-03, -5.31497140e-03), + Complex::new(-4.30880421e-03, 4.75403284e-03), + Complex::new(-1.12464745e-02, -4.65844227e-03), + Complex::new(1.60156679e-04, -3.26006409e-03), + Complex::new(-1.03362126e-02, 3.13545581e-03), + Complex::new(-7.69974139e-03, -1.03818994e-02), + Complex::new(-5.76821480e-04, 8.63274351e-04), + Complex::new(-1.50337277e-02, -3.76575276e-03), + Complex::new(-1.19695644e-03, -1.21529027e-02), + Complex::new(-6.42978800e-03, 3.04106324e-03), + Complex::new(-1.42967043e-02, -1.42967043e-02), + Complex::new(4.02680910e-03, -8.51397251e-03), + Complex::new(-1.52764230e-02, -1.50459634e-03), + Complex::new(-5.98961717e-03, -2.39118921e-02), + Complex::new(2.95727524e-03, -1.97598814e-03), + Complex::new(-2.14271696e-02, -1.58914720e-02), + Complex::new(8.26910313e-03, -2.72595798e-02), + Complex::new(-7.54577227e-03, 3.70700021e-04), + Complex::new(-1.67955192e-02, -4.05479701e-02), + Complex::new(2.33021328e-02, -2.11198221e-02), + Complex::new(-2.74472271e-02, -1.46708486e-02), + Complex::new(1.15826806e-02, -7.80840900e-02), + Complex::new(3.26800996e-02, -6.50047599e-03), + Complex::new(-6.05385232e-02, -1.01002424e-01), + Complex::new(1.84598172e-01, -2.24933523e-01), + Complex::new(3.45823071e-01, 1.23737473e-01), + Complex::new(-7.14505905e-17, 2.90983805e-01), + Complex::new(-1.10872171e-01, 3.96706970e-02), + Complex::new(2.11382003e-02, 2.57569716e-02), + Complex::new(-4.05824891e-02, 6.77077926e-02), + Complex::new(-3.05240813e-02, -6.07161727e-03), + Complex::new(4.61451895e-03, 3.11085599e-02), + Complex::new(-3.87064718e-02, 2.06890402e-02), + Complex::new(-5.59779124e-03, -5.07354224e-03), + Complex::new(-1.09011912e-02, 2.63178036e-02), + Complex::new(-2.66448692e-02, -1.30897849e-03), + Complex::new(1.03245107e-03, 3.40353504e-03), + Complex::new(-1.97995805e-02, 1.46843697e-02), + Complex::new(-1.27633405e-02, -8.52819147e-03), + Complex::new(-2.28844145e-03, 9.13597039e-03), + Complex::new(-2.01212351e-02, 1.98176868e-03), + Complex::new(-3.04106324e-03, -6.42978800e-03), + Complex::new(-8.63497967e-03, 8.63497967e-03), + Complex::new(-1.40101969e-02, -6.62632965e-03), + Complex::new(1.01766417e-04, -1.03325177e-03), + Complex::new(-1.25381879e-02, 3.14065256e-03), + Complex::new(-6.00088826e-03, -8.98096395e-03), + Complex::new(-1.94435998e-03, 2.62166594e-03), + Complex::new(-1.16489269e-02, -3.53366333e-03), + Complex::new(-3.14824186e-04, -6.40839353e-03), + Complex::new(5.56783637e-03, 2.30627334e-03), + Complex::new(-7.00257479e-03, -7.72615067e-03), + Complex::new(1.21764617e-03, -2.27805575e-03), + Complex::new(-7.37249766e-03, -1.09360672e-03), + Complex::new(-1.57881619e-03, -7.93724499e-03), + Complex::new(-4.48820552e-04, 2.69012686e-04), + Complex::new(-6.00109974e-03, -4.92497528e-03), + ]) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..7f823b5 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,193 @@ +#![no_main] +#![no_std] +use defmt_rtt as _; // global logger + +use panic_probe as _; +use stm32f4xx_hal as _; + +// same panicking *behavior* as `panic-probe` but doesn't print a panic message +// this prevents the panic message being printed *twice* when `defmt::panic` is invoked +#[defmt::panic_handler] +fn panic() -> ! { + cortex_m::asm::udf() +} + +use rtic::app; + +mod filters; +mod si5153; + +#[app(device = stm32f4xx_hal::pac, peripherals = true, dispatchers = [SPI3])] +mod app { + + use num::Complex; + use stm32f4xx_hal::{ + adc::{self, config::AdcConfig}, + gpio::{self, gpioa, gpioc, Analog, Output, PushPull}, + i2c::{self, I2c}, + pac::{ADC1, I2C1, TIM2, TIM4}, + prelude::*, + timer::{ + self, Channel, Channel1, Channel3, ChannelBuilder, CounterHz, Event, PwmHz, + }, + }; + + use systick_monotonic::Systick; + + + use num_traits::float::Float; + + use crate::filters; + use crate::si5153; + + type AudioPwm = PwmHz>; + + #[monotonic(binds = SysTick, default = true)] + type MonoTimer = Systick<1_000>; + + #[shared] + struct Shared {} + + #[local] + struct Local { + pll: si5153::Si5153>, + i2c: I2c, + board_led: gpioc::PC13>, + rx_en: gpioa::PA7>, + adc1: adc::Adc, + mic_in: gpio::Pin<'A', 4, Analog>, + i_in: gpio::Pin<'A', 1, Analog>, + q_in: gpio::Pin<'A', 0, Analog>, + phase: f32, + i_offset: f32, + q_offset: f32, + audio_pwm: AudioPwm, + timer: CounterHz, + usb_filter: filters::FirFilter<63>, + } + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let rcc = cx.device.RCC.constrain(); + + // Freeze the configuration of all the clocks in the system and store the frozen frequencies in + // `clocks` + let clocks = rcc + .cfgr + .use_hse(8.MHz()) + .sysclk(72.MHz()) + .pclk1(36.MHz()) + .freeze(); + + defmt::info!("Clock Setup done"); + + let mono = Systick::new(cx.core.SYST, clocks.sysclk().to_Hz()); + + // Acquire the GPIOC peripheral + let gpioa = cx.device.GPIOA.split(); + let gpiob = cx.device.GPIOB.split(); + let gpioc = cx.device.GPIOC.split(); + + let board_led = gpioc.pc13.into_push_pull_output(); + + let scl = gpiob.pb6.into_alternate_open_drain(); + let sda = gpiob.pb7.into_alternate_open_drain(); + let mut i2c = i2c::I2c::new( + cx.device.I2C1, + (scl, sda), + i2c::Mode::Standard { + frequency: 400.kHz(), + }, + &clocks, + ); + + let mut pll = si5153::Si5153::new(&i2c); + pll.init(&mut i2c, 25000000, 800000000, 800000000); + pll.set_ms_source(&mut i2c, si5153::Multisynth::MS0, si5153::PLL::A); + pll.set_ms_source(&mut i2c, si5153::Multisynth::MS1, si5153::PLL::A); + pll.set_ms_source(&mut i2c, si5153::Multisynth::MS2, si5153::PLL::B); + + pll.set_ms_freq(&mut i2c, si5153::Multisynth::MS0, 8_000_000); + pll.set_ms_phase(&mut i2c, si5153::Multisynth::MS0, 0); + pll.enable_ms_output(&mut i2c, si5153::Multisynth::MS0); + + pll.set_ms_freq(&mut i2c, si5153::Multisynth::MS1, 8_000_000); + pll.set_ms_phase(&mut i2c, si5153::Multisynth::MS1, 100); + pll.enable_ms_output(&mut i2c, si5153::Multisynth::MS1); + + let adc1 = adc::Adc::adc1(cx.device.ADC1, true, AdcConfig::default()); + let mic_in = gpioa.pa4.into_analog(); + + let i_in = gpioa.pa1.into_analog(); + let q_in = gpioa.pa0.into_analog(); + + let audio_out = Channel3::new(gpiob.pb8); + let mut audio_pwm = cx.device.TIM4.pwm_hz(audio_out, 192.kHz(), &clocks); + audio_pwm.enable(Channel::C3); + audio_pwm.set_duty(Channel::C3, 0u16); + + let mut rx_en = gpioa.pa7.into_push_pull_output(); + rx_en.set_high(); + + let bias_pin = Channel1::new(gpioa.pa6); + let _bias_pwm = cx.device.TIM3.pwm_hz(bias_pin, 64.kHz(), &clocks); + + let mut timer = timer::Timer2::new(cx.device.TIM2, &clocks).counter_hz(); + timer.start(6400.Hz()).unwrap(); + // Generate an interrupt when the timer expires + timer.listen(Event::Update); + + ( + Shared {}, + Local { + i2c, + pll, + board_led, + rx_en, + adc1, + mic_in, + i_in, + q_in, + phase: 0.0, + i_offset: 2048.0, + q_offset: 2048.0, + audio_pwm, + timer, + usb_filter: filters::usb_firfilter(), + }, + init::Monotonics(mono), + ) + } + + #[task(binds=TIM2, local=[timer, pll, i2c, adc1, mic_in, i_in, q_in, audio_pwm, phase, i_offset, q_offset, board_led, usb_filter])] + fn transmit(ctx: transmit::Context) { + ctx.local.board_led.toggle(); + + let adc = ctx.local.adc1; + let i_in = ctx.local.i_in; + let q_in = ctx.local.q_in; + + let i_raw: u16 = adc.read(&mut *q_in).unwrap(); + let q_raw: u16 = adc.read(&mut *i_in).unwrap(); + + *ctx.local.i_offset = 0.95 * *ctx.local.i_offset + 0.05 * (i_raw as f32); + *ctx.local.q_offset = 0.95 * *ctx.local.q_offset + 0.05 * (q_raw as f32); + + let i_sample = (i_raw as f32) - *ctx.local.i_offset; + let q_sample = (q_raw as f32) - *ctx.local.q_offset; + + let sample = Complex::new(i_sample as f32 / 4096.0, q_sample as f32 / 4096.0); + let filtered = ctx.local.usb_filter.compute(sample) * 2.0; + + let max_duty = if ctx.local.audio_pwm.get_max_duty() != 0 { + ctx.local.audio_pwm.get_max_duty() as f32 + } else { + 2.0.powi(16) + }; + + let output = filtered.re * max_duty; + ctx.local.audio_pwm.set_duty(Channel::C3, output as u16); + + ctx.local.timer.clear_interrupt(Event::Update); + } +} diff --git a/src/si5153.rs b/src/si5153.rs new file mode 100644 index 0000000..6d23f24 --- /dev/null +++ b/src/si5153.rs @@ -0,0 +1,199 @@ +use core::marker::PhantomData; +use embedded_hal::blocking::i2c; + +const I2C_ADDR: u8 = 96; + +const CLK_ENABLE_CONTROL: u8 = 3; +//const PLLX_SRC: u8 = 15; +const PLL_RESET: u8 = 177; +const XTAL_LOAD_CAP: u8 = 183; + +#[derive(PartialEq, Copy, Clone)] +pub enum PLL { + A, + B, +} + +const PLL_BASE_ADDR: [u8; 2] = [26, 34]; + +impl PLL { + fn base_address(&self) -> u8 { + return PLL_BASE_ADDR[*self as usize]; + } +} + +#[derive(Copy, Clone)] +pub enum Multisynth { + MS0, + MS1, + MS2, +} + +const MS_BASE_ADDR: [u8; 3] = [42, 50, 58]; +const MS_CTRL_ADDR: [u8; 3] = [16, 17, 18]; +const CLK_PHOFF_ADDR: [u8; 3] = [165, 166, 167]; + +impl Multisynth { + fn base_address(&self) -> u8 { + return MS_BASE_ADDR[*self as usize]; + } + + fn ctrl_address(&self) -> u8 { + return MS_CTRL_ADDR[*self as usize]; + } + + fn phoff_address(&self) -> u8 { + defmt::debug!("Adress: {}", CLK_PHOFF_ADDR[*self as usize]); + return CLK_PHOFF_ADDR[*self as usize]; + } +} + +pub struct PllParams { + pub p1: u32, + pub p2: u32, + pub p3: u32, +} + +pub struct Si5153 { + // Marker that makes sure we always get the same I2C + i2c: PhantomData, + pll_freqs: [u32; 2], + outputs: u8, + ms_srcs: [PLL; 3], +} + +impl Si5153 +where + I2C: i2c::Write, +{ + pub fn new(_i2c: &I2C) -> Self { + Si5153 { + i2c: PhantomData, + pll_freqs: [0, 0], + outputs: 0, + ms_srcs: [PLL::A, PLL::A, PLL::A], + } + } + + pub fn init(&mut self, i2c: &mut I2C, freq_xtal: u32, freq_a: u32, freq_b: u32) { + self.pll_freqs[PLL::A as usize] = freq_a; + self.pll_freqs[PLL::B as usize] = freq_b; + + self.outputs = 0xFF; + self.write_byte_reg(i2c, CLK_ENABLE_CONTROL, self.outputs); // Disable all outputs + self.write_byte_reg(i2c, XTAL_LOAD_CAP, 0xD2); //crystal load capacitor = 10pF + + for ms in [Multisynth::MS0, Multisynth::MS1, Multisynth::MS2].iter() { + self.ms_srcs[*ms as usize] = PLL::A; + self.write_byte_reg(i2c, ms.ctrl_address(), 0x0F); // MSi as Source, PLLA to MSi, 8 mA output + self.write_byte_reg(i2c, ms.phoff_address(), 0); // Phase offset to 0. + } + + for pll in [PLL::A, PLL::B].iter() { + let fdiv = self.pll_freqs[*pll as usize] / freq_xtal; + let rm = self.pll_freqs[*pll as usize] % freq_xtal; + + //TODO: Find better way to determine c and b + let c = 0x0FFFFF; + let a = fdiv; + let b = ((rm as u64) * (c as u64) / (freq_xtal as u64)) as u32; + + let params = PllParams { + p1: 128 * a + (128 * b / c) - 512, + p2: 128 * b - c * (128 * b / c), + p3: c, + }; + + self.write_params(i2c, pll.base_address(), ¶ms) + } + + self.write_byte_reg(i2c, PLL_RESET, 0xA0); // Reset both PLLs + } + + pub fn enable_ms_output(&mut self, i2c: &mut I2C, synth: Multisynth) { + self.outputs &= !(1 << (synth as u8)); + self.write_byte_reg(i2c, CLK_ENABLE_CONTROL, self.outputs); + } + + pub fn disable_ms_output(&mut self, i2c: &mut I2C, synth: Multisynth) { + self.outputs |= 1 << (synth as u8); + self.write_byte_reg(i2c, CLK_ENABLE_CONTROL, self.outputs); + } + + pub fn set_ms_source(&mut self, i2c: &mut I2C, synth: Multisynth, pll: PLL) { + let value: u8 = if pll == PLL::A { + self.ms_srcs[synth as usize] = PLL::A; + 0x0F // MS as Source, PLLA to MS, 8 mA output + } else { + self.ms_srcs[synth as usize] = PLL::B; + 0x2F // MS as Source, PLLB to MS, 8 mA output + }; + + self.write_byte_reg(i2c, synth.ctrl_address(), value); + } + + pub fn set_ms_freq(&mut self, i2c: &mut I2C, synth: Multisynth, freq: u32) { + let pll = self.ms_srcs[synth as usize]; + let fdiv = self.pll_freqs[pll as usize] / freq; + let rm = self.pll_freqs[pll as usize] % freq; + + //TODO: Find better way to determine c and b + let c: u32 = 0x0FFFFF; + let a: u32 = fdiv; + let b: u32 = ((rm as u64) * (c as u64) / (freq as u64)) as u32; + + let params = PllParams { + p1: 128 * a + (128 * b / c) - 512, + p2: 128 * b - c * (128 * b / c), + p3: c, + }; + self.write_params(i2c, synth.base_address(), ¶ms) + } + + pub fn set_ms_phase(&mut self, i2c: &mut I2C, synth: Multisynth, phase: u8) { + self.write_byte_reg(i2c, synth.phoff_address(), phase); + + match self.ms_srcs[synth as usize] { + PLL::A => { + self.write_byte_reg(i2c, PLL_RESET, 1 << 5); + } + PLL::B => { + self.write_byte_reg(i2c, PLL_RESET, 1 << 7); + } + } + } + + pub fn pll_reset(&mut self, i2c: &mut I2C) { + self.write_byte_reg(i2c, PLL_RESET, 0xA0); + } + + fn write_byte_reg(&self, i2c: &mut I2C, reg_addr: u8, data: u8) { + let res = i2c.write(I2C_ADDR, &[reg_addr, data]); + if res.is_err() { + panic!("i2c write failed. regAdder: {}", reg_addr) + } + } + + fn write_params(&self, i2c: &mut I2C, base: u8, params: &PllParams) { + let data: [u8; 9] = [ + base, + ((params.p3 & 0x00FF00) >> 8) as u8, + (params.p3 & 0x0000FF) as u8, + ((params.p1 & 0x030000) >> 16) as u8, + ((params.p1 & 0x00FF00) >> 8) as u8, + (params.p1 & 0x0000FF) as u8, + (((params.p3 & 0x0F0000) >> 12) | ((params.p2 & 0x0F0000) >> 16)) as u8, + ((params.p2 & 0x00FF00) >> 8) as u8, + (params.p2 & 0x0000FF) as u8, + ]; + + let res = i2c.write(I2C_ADDR, &data); + if res.is_err() { + panic!("i2c write failed. regAdder: {}", base) + } + } + + pub fn write_synth_params(&self, i2c: &mut I2C, synth: Multisynth, params: &PllParams) { + self.write_params(i2c, synth.base_address(), params); + } +} diff --git a/ssb_filter.py b/ssb_filter.py new file mode 100644 index 0000000..a31fdac --- /dev/null +++ b/ssb_filter.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +from scipy import signal +import matplotlib.pyplot as plt +import numpy as np + + +def main(): + fs = 6400.0 + coeffs = signal.firls(63, (0, 1150, 1200, fs/2), (1, 1, 0, 0), fs=fs) + + freq_space = np.linspace(-fs/2 / (fs/2)*np.pi, fs/2 / (fs/2)*np.pi, 512) + freqs, response = signal.freqz(coeffs, worN=freq_space) + response = 10 * np.log(abs(response)) + plt.plot(fs/2*freqs/(np.pi), response) + plt.grid() + plt.show() + + f0 = (1200 + 50) / fs + complex_coeffs = [] + for n in range(0, len(coeffs)): + complex_coeffs += [coeffs[n] * np.exp(1j * 2 * np.pi * f0 * n)] + complex_coeffs = np.array(complex_coeffs) + + print(complex_coeffs) + + freq_space = np.linspace(-fs/2 / (fs/2)*np.pi, fs/2 / (fs/2)*np.pi, 512) + freqs, response = signal.freqz(complex_coeffs, worN=freq_space) + response = 10 * np.log(abs(response)) + plt.plot(fs/2*freqs/(np.pi), response) + plt.grid() + plt.show() + + + +if __name__ == '__main__': + main() \ No newline at end of file