diff --git a/Cargo.toml b/Cargo.toml index 736149cdad35f5482777c9b851eecd1b2d8daddc..95785019253a45c2894ed5984ecc2aedf22e5dc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ panic-halt = "0.2.0" #panic-itm = "0.4.2" # Uncomment for the rtt-timing example. -panic-rtt-target = { version = "0.1.1", features = ["cortex-m"] } +# panic-rtt-target = { version = "0.1.1", features = ["cortex-m"] } # Uncomment for the panic example. #panic-semihosting = "0.5.6" diff --git a/README.md b/README.md index 4b61f4501b43919c65719d57940efbf8fe1ed1ab..a6655f0780d165f43dde4c5b8ebf55744222841a 100644 --- a/README.md +++ b/README.md @@ -26,24 +26,33 @@ - [USB Cable] -| Signl | Color | Pin | -| ----- | ----- | ---- | -| V+ | Red | ---- | -| D- | White | PA11 | -| D+ | Green | PA12 | -| Gnd | Black | ---- | +| Signal | Color | Pin | +| ------ | ----- | ---- | +| V+ | Red | ---- | +| D- | White | PA11 | +| D+ | Green | PA12 | +| Gnd | Black | ---- | D+ used for re-enumeration +--- + ## Debug interface - Serial Wire debugging uses pins PA13 and PA14. So refrain from using those unless absolutely necessary. +--- + +## Examples + + +--- + ## Troubleshooting ### Fail to connect with openocd -First check that your stilnk nucleo programmer is found by the host. +First check that your stlink nucleo programmer is found by the host. ```shell > lsusb diff --git a/build.rs b/build.rs index d534cc3df609de30befec7eda32c4b44ab7ed63e..1b9fd2cfc35a12ebc1460e4b7e2ee3218c3c7b84 100644 --- a/build.rs +++ b/build.rs @@ -8,12 +8,15 @@ //! updating `memory.x` ensures a rebuild of the application with the //! new memory settings. +use core::f64::consts::PI; use std::env; use std::fs::File; -use std::io::Write; -use std::path::PathBuf; +use std::{ + io::{Result, Write}, + path::{Path, PathBuf}, +}; -fn main() { +fn main() -> Result<()> { // Put `memory.x` in our output directory and ensure it's // on the linker search path. let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); @@ -28,4 +31,24 @@ fn main() { // here, we ensure the build script is only re-run when // `memory.x` is changed. println!("cargo:rerun-if-changed=memory.x"); + + // generate a sine table + println!("cargo:rerun-if-changed=build.rs"); + let out_dir = env::var("OUT_DIR").unwrap(); + let dest_path = Path::new(&out_dir).join("sin_abs_const.rs"); + let mut f = File::create(&dest_path).unwrap(); + + const SINE_BUF_SIZE: usize = 256; + write!(f, "const SINE_BUF_SIZE: usize = {};\n", SINE_BUF_SIZE)?; + write!(f, "const SINE_BUF: [u8; SINE_BUF_SIZE] = [")?; + + for i in 0..SINE_BUF_SIZE { + let s = ((i as f64) * 2.0 * PI / SINE_BUF_SIZE as f64).sin(); + let v = (128.0 + 128.0 * s) as u8; + + write!(f, " {},", v)?; + } + write!(f, "];\n")?; + + Ok(()) } diff --git a/examples/rtt-pwm-dma.rs b/examples/rtt-pwm-dma.rs index 1c169a947d2c76af5f81741befc345ba60b0c0b6..de6512e444de3e9b401715d2dd30d392d6dd5b1c 100644 --- a/examples/rtt-pwm-dma.rs +++ b/examples/rtt-pwm-dma.rs @@ -103,6 +103,60 @@ const APP: () = { .set_bit() }); + // Setup TIMER2 + let tim2 = dp.TIM2; + // Here we need unsafe as we are "stealing" the RCC peripheral + // At this point it has been contrained into SysConf and used to set clocks + let rcc = unsafe { &(*stm32::RCC::ptr()) }; + + rcc.apb1enr.modify(|_, w| w.tim2en().set_bit()); + rcc.apb1rstr.modify(|_, w| w.tim2rst().set_bit()); + rcc.apb1rstr.modify(|_, w| w.tim2rst().clear_bit()); + + // auto-reload, preload + tim1.cr1.modify(|_, w| w.arpe().set_bit()); + + let clk = clocks.pclk2().0 * if clocks.ppre2() == 1 { 1 } else { 2 }; + // check that its actually 48_000_000 + rprintln!("clk {}", clk); + + // assume we have a sine at + let pre = 0; + rprintln!("pre {}", pre); + tim2.psc.write(|w| w.psc().bits(pre)); + + // we want 8 bits of resolution + // so our ARR = 2^8 - 1 = 256 - 1 = 255 + let arr = 255; + rprintln!("arr {}", arr); + tim1.arr.write(|w| unsafe { w.bits(arr) }); + + // Trigger update event to load the registers + tim1.cr1.modify(|_, w| w.urs().set_bit()); + tim1.egr.write(|w| w.ug().set_bit()); + tim1.cr1.modify(|_, w| w.urs().clear_bit()); + + // Set main output enable of all Output Compare (OC) registers + tim1.bdtr.modify(|_, w| w.moe().set_bit()); + + // Set output enable for channels 1 and 2 + // Channel 1 (bit 0) + unsafe { bb::set(&tim1.ccer, 0) } + // Channel 4 (bit 0) + unsafe { bb::set(&tim1.ccer, 4) } + + // Setup the timer + tim1.cr1.write(|w| { + w.cms() + .bits(0b00) // edge aligned mode + .dir() // counter used as upcounter + .clear_bit() + .opm() // one pulse mode + .clear_bit() + .cen() // enable counter + .set_bit() + }); + // Set duty cycle of Channels tim1.ccr1.write(|w| unsafe { w.ccr().bits(128) }); tim1.ccr2.write(|w| unsafe { w.ccr().bits(128) }); @@ -119,14 +173,21 @@ const APP: () = { rprintln!("here"); tim1.sr.modify(|_, w| w.uif().clear()); + let mut dma_buff = [0u8; 256]; + for i in 0..256 { + dma_buff[i] = i as u8; + } + loop { for i in 0..256 { // wait until next update event while tim1.sr.read().uif().is_clear() {} tim1.sr.modify(|_, w| w.uif().clear()); - tim1.ccr1.write(|w| unsafe { w.ccr().bits(i) }); - tim1.ccr2.write(|w| unsafe { w.ccr().bits(i) }); + tim1.ccr1 + .write(|w| unsafe { w.ccr().bits(dma_buff[i] as u16) }); + tim1.ccr2 + .write(|w| unsafe { w.ccr().bits(dma_buff[i] as u16) }); } } } diff --git a/examples/rtt-pwm-sine.rs b/examples/rtt-pwm-sine.rs new file mode 100644 index 0000000000000000000000000000000000000000..75d4880bdaed48a5ae5f3f1bdce932119529caea --- /dev/null +++ b/examples/rtt-pwm-sine.rs @@ -0,0 +1,219 @@ +//! examples/rtt-pwm-sine.rs +//! cargo run --examples rtt-pwm-sine + +// #![deny(unsafe_code)] +// #![deny(warnings)] +#![no_main] +#![no_std] + +use core::f32::consts::PI; +use cortex_m::{asm, peripheral::DWT}; +use panic_halt as _; +use rtt_target::{rprint, rprintln, rtt_init_print}; + +use stm32f4xx_hal::{bb, dma, gpio::Speed, prelude::*, pwm, stm32}; + +include!(concat!(env!("OUT_DIR"), "/sin_abs_const.rs")); + +#[rtic::app(device = stm32f4xx_hal::stm32, peripherals = true)] +const APP: () = { + #[init] + fn init(mut cx: init::Context) { + rtt_init_print!(); + rprintln!("init"); + let dp = cx.device; + + // Initialize (enable) the monotonic timer (CYCCNT) + cx.core.DCB.enable_trace(); + cx.core.DWT.enable_cycle_counter(); + + let rcc = dp.RCC.constrain(); + // Set up the system clock. 48 MHz? + let clocks = rcc + .cfgr + // .use_hse(8.mhz()) + .sysclk(48.mhz()) + .pclk1(24.mhz()) + .freeze(); + + let gpioa = dp.GPIOA.split(); + // we set the pins to VeryHigh to get the sharpest waveform possible + // (rise and fall times should have similar characteristics) + let channels = ( + gpioa.pa8.into_alternate_af1().set_speed(Speed::VeryHigh), + gpioa.pa9.into_alternate_af1().set_speed(Speed::VeryHigh), + ); + + // Setup PWM RAW + let tim1 = dp.TIM1; + // Here we need unsafe as we are "stealing" the RCC peripheral + // At this point it has been contrained into SysConf and used to set clocks + let rcc = unsafe { &(*stm32::RCC::ptr()) }; + + rcc.apb2enr.modify(|_, w| w.tim1en().set_bit()); + rcc.apb2rstr.modify(|_, w| w.tim1rst().set_bit()); + rcc.apb2rstr.modify(|_, w| w.tim1rst().clear_bit()); + + // Setup chanel 1 and 2 as pwm_mode1 + tim1.ccmr1_output() + .modify(|_, w| w.oc1pe().set_bit().oc1m().pwm_mode1()); + + tim1.ccmr1_output() + .modify(|_, w| w.oc2pe().set_bit().oc2m().pwm_mode1()); + + // The reference manual is a bit ambiguous about when enabling this bit is really + // necessary, but since we MUST enable the preload for the output channels then we + // might as well enable for the auto-reload too + tim1.cr1.modify(|_, w| w.arpe().set_bit()); + + let clk = clocks.pclk2().0 * if clocks.ppre2() == 1 { 1 } else { 2 }; + // check that its actually 48_000_000 + rprintln!("clk {}", clk); + + // we want maximum performance, thus we set the prescaler to 0 + let pre = 0; + rprintln!("pre {}", pre); + tim1.psc.write(|w| w.psc().bits(pre)); + + // we want 8 bits of resolution + // so our ARR = 2^8 - 1 = 256 - 1 = 255 + let arr = 255; + rprintln!("arr {}", arr); + tim1.arr.write(|w| unsafe { w.bits(arr) }); + + // Trigger update event to load the registers + tim1.cr1.modify(|_, w| w.urs().set_bit()); + tim1.egr.write(|w| w.ug().set_bit()); + tim1.cr1.modify(|_, w| w.urs().clear_bit()); + + // Set main output enable of all Output Compare (OC) registers + tim1.bdtr.modify(|_, w| w.moe().set_bit()); + + // Set output enable for channels 1 and 2 + // Channel 1 (bit 0) + unsafe { bb::set(&tim1.ccer, 0) } + // Channel 4 (bit 0) + unsafe { bb::set(&tim1.ccer, 4) } + + // Setup the timer + tim1.cr1.write(|w| { + w.cms() + .bits(0b00) // edge aligned mode + .dir() // counter used as upcounter + .clear_bit() + .opm() // one pulse mode + .clear_bit() + .cen() // enable counter + .set_bit() + }); + + // Setup TIMER2 + let tim2 = dp.TIM2; + // Here we need unsafe as we are "stealing" the RCC peripheral + // At this point it has been contrained into SysConf and used to set clocks + let rcc = unsafe { &(*stm32::RCC::ptr()) }; + + rcc.apb1enr.modify(|_, w| w.tim2en().set_bit()); + rcc.apb1rstr.modify(|_, w| w.tim2rst().set_bit()); + rcc.apb1rstr.modify(|_, w| w.tim2rst().clear_bit()); + + // auto-reload, preload + tim1.cr1.modify(|_, w| w.arpe().set_bit()); + + let clk = clocks.pclk2().0 * if clocks.ppre2() == 1 { 1 } else { 2 }; + // check that its actually 48_000_000 + rprintln!("clk {}", clk); + + // assuinclude!(concat!(env!("OUT_DIR"), "/sin.rs"));c.write(|w| w.psc().bits(pre)); + + // we want 8 bits of resolution + // so our ARR = 2^8 - 1 = 256 - 1 = 255 + let arr = 255; + rprintln!("arr {}", arr); + tim1.arr.write(|w| unsafe { w.bits(arr) }); + + // Trigger update event to load the registers + tim1.cr1.modify(|_, w| w.urs().set_bit()); + tim1.egr.write(|w| w.ug().set_bit()); + tim1.cr1.modify(|_, w| w.urs().clear_bit()); + + // Set main output enable of all Output Compare (OC) registers + tim1.bdtr.modify(|_, w| w.moe().set_bit()); + + // Set output enable for channels 1 and 2 + // Channel 1 (bit 0) + unsafe { bb::set(&tim1.ccer, 0) } + // Channel 4 (bit 0) + unsafe { bb::set(&tim1.ccer, 4) } + + // Setup the timer + tim1.cr1.write(|w| { + w.cms() + .bits(0b00) // edge aligned mode + .dir() // counter used as upcounter + .clear_bit() + .opm() // one pulse mode + .clear_bit() + .cen() // enable counter + .set_bit() + }); + + // Set duty cycle of Channels + tim1.ccr1.write(|w| unsafe { w.ccr().bits(128) }); + tim1.ccr2.write(|w| unsafe { w.ccr().bits(128) }); + + // Set preload for the CCx + tim1.cr2.write(|w| w.ccpc().set_bit()); + + tim1.dier.write(|w| w.uie().enabled()); + tim1.sr.modify(|_, w| w.uif().clear()); + + // Set divider to 4, (48_000_000/256)/4 + tim1.rcr.modify(|_, w| unsafe { w.rep().bits(4) }); + + while tim1.sr.read().uif().is_clear() { + rprint!("-"); + } + rprintln!("here"); + tim1.sr.modify(|_, w| w.uif().clear()); + + loop { + for i in 0..SINE_BUF_SIZE { + // wait until next update event + + while tim1.sr.read().uif().is_clear() {} + tim1.sr.modify(|_, w| w.uif().clear()); + + tim1.ccr1 + .write(|w| unsafe { w.ccr().bits(SINE_BUF[i] as u16) }); + tim1.ccr2 + .write(|w| unsafe { w.ccr().bits(SINE_BUF[i] as u16) }); + } + } + } + + // [task(resources = [GPIOA], schedule = [toggle])] + // fn toggle(cx: toggle::Context) { + // static mut TOGGLE: bool = false; + // hprintln!("foo @ {:?}", Instant::now()).unwrap(); + + // if *TOGGLE { + // cx.resources.GPIOA.bsrr.write(|w| w.bs5().set_bit()); + // } else { + // cx.resources.GPIOA.bsrr.write(|w| w.br5().set_bit()); + // } + + // *TOGGLE = !*TOGGLE; + // cx.schedule + // .toggle(cx.scheduled + 8_000_000.cycles()) + // .unwrap(); + // } + + #[idle] + fn idle(_cx: idle::Context) -> ! { + rprintln!("idle"); + loop { + continue; + } + } +};