diff --git a/embassy-stm32/src/timer/input_capture.rs b/embassy-stm32/src/timer/input_capture.rs new file mode 100644 index 000000000..6401656c8 --- /dev/null +++ b/embassy-stm32/src/timer/input_capture.rs @@ -0,0 +1,141 @@ +//! Input capture driver. + +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::channel; + +use super::low_level::{CountingMode, InputCaptureMode, InputTISelection, Timer}; +use super::{Channel, Channel1Pin, Channel2Pin, Channel3Pin, Channel4Pin, GeneralInstance4Channel}; +use crate::gpio::{AFType, AnyPin, Pull}; +use crate::time::Hertz; +use crate::Peripheral; + +/// Channel 1 marker type. +pub enum Ch1 {} +/// Channel 2 marker type. +pub enum Ch2 {} +/// Channel 3 marker type. +pub enum Ch3 {} +/// Channel 4 marker type. +pub enum Ch4 {} + +/// Capture pin wrapper. +/// +/// This wraps a pin to make it usable with capture. +pub struct CapturePin<'d, T, C> { + _pin: PeripheralRef<'d, AnyPin>, + phantom: PhantomData<(T, C)>, +} + +macro_rules! channel_impl { + ($new_chx:ident, $channel:ident, $pin_trait:ident) => { + impl<'d, T: GeneralInstance4Channel> CapturePin<'d, T, $channel> { + #[doc = concat!("Create a new ", stringify!($channel), " capture pin instance.")] + pub fn $new_chx(pin: impl Peripheral

> + 'd, pull_type: Pull) -> Self { + into_ref!(pin); + critical_section::with(|_| { + pin.set_as_af_pull(pin.af_num(), AFType::Input, pull_type); + #[cfg(gpio_v2)] + pin.set_speed(crate::gpio::Speed::VeryHigh); + }); + CapturePin { + _pin: pin.map_into(), + phantom: PhantomData, + } + } + } + }; +} + +channel_impl!(new_ch1, Ch1, Channel1Pin); +channel_impl!(new_ch2, Ch2, Channel2Pin); +channel_impl!(new_ch3, Ch3, Channel3Pin); +channel_impl!(new_ch4, Ch4, Channel4Pin); + +/// Input capture driver. +pub struct InputCapture<'d, T: GeneralInstance4Channel> { + inner: Timer<'d, T>, +} + +impl<'d, T: GeneralInstance4Channel> InputCapture<'d, T> { + /// Create a new input capture driver. + pub fn new( + tim: impl Peripheral

+ 'd, + _ch1: Option>, + _ch2: Option>, + _ch3: Option>, + _ch4: Option>, + freq: Hertz, + counting_mode: CountingMode, + ) -> Self { + Self::new_inner(tim, freq, counting_mode) + } + + fn new_inner(tim: impl Peripheral

+ 'd, freq: Hertz, counting_mode: CountingMode) -> Self { + let mut this = Self { inner: Timer::new(tim) }; + + this.inner.set_counting_mode(counting_mode); + this.set_tick_freq(freq); + this.inner.enable_outputs(); // Required for advanced timers, see GeneralInstance4Channel for details + this.inner.start(); + + [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4] + .iter() + .for_each(|&channel| { + this.inner.set_input_capture_mode(channel, InputCaptureMode::Rising); + + this.inner.set_input_ti_selection(channel, InputTISelection::Normal); + }); + + this + } + + /// Enable the given channel. + pub fn enable(&mut self, channel: Channel) { + self.inner.enable_channel(channel, true); + } + + /// Disable the given channel. + pub fn disable(&mut self, channel: Channel) { + self.inner.enable_channel(channel, false); + } + + /// Check whether given channel is enabled + pub fn is_enabled(&self, channel: Channel) -> bool { + self.inner.get_channel_enable_state(channel) + } + + /// Set tick frequency. + /// + /// Note: when you call this, the max period value changes + pub fn set_tick_freq(&mut self, freq: Hertz) { + let f = freq; + assert!(f.0 > 0); + let timer_f = self.inner.get_clock_frequency(); + + let pclk_ticks_per_timer_period = timer_f / f; + let psc: u16 = unwrap!((pclk_ticks_per_timer_period - 1).try_into()); + + let regs = self.inner.regs_core(); + regs.psc().write_value(psc); + + // Generate an Update Request + regs.egr().write(|r| r.set_ug(true)); + } + + /// Set the input capture mode for a given channel. + pub fn set_input_capture_mode(&mut self, channel: Channel, mode: InputCaptureMode) { + self.inner.set_input_capture_mode(channel, mode); + } + + /// Set input TI selection. + pub fn set_input_ti_selection(&mut self, channel: Channel, tisel: InputTISelection) { + self.inner.set_input_ti_selection(channel, tisel) + } + + /// Get capture value for a channel. + pub fn get_capture_value(&self, channel: Channel) -> u32 { + self.inner.get_capture_value(channel) + } +} diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index 346127005..532d41650 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs @@ -5,6 +5,7 @@ pub mod complementary_pwm; pub mod low_level; pub mod qei; pub mod simple_pwm; +pub mod input_capture; use crate::interrupt; use crate::rcc::RccPeripheral; diff --git a/examples/stm32f4/.cargo/config.toml b/examples/stm32f4/.cargo/config.toml index 16efa8e6f..fdb246a7f 100644 --- a/examples/stm32f4/.cargo/config.toml +++ b/examples/stm32f4/.cargo/config.toml @@ -1,9 +1,17 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] # replace STM32F429ZITx with your chip as listed in `probe-rs chip list` -runner = "probe-rs run --chip STM32F429ZITx" +# runner = "probe-rs run --chip STM32F429ZITx" +runner = "arm-none-eabi-gdb -q -x openocd.gdb" [build] -target = "thumbv7em-none-eabi" +# Pick ONE of these default compilation targets +# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ +# target = "thumbv7m-none-eabi" # Cortex-M3 +# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) +# target = "thumbv8m.base-none-eabi" # Cortex-M23 +# target = "thumbv8m.main-none-eabi" # Cortex-M33 (no FPU) +# target = "thumbv8m.main-none-eabihf" # Cortex-M33 (with FPU) [env] DEFMT_LOG = "trace" diff --git a/examples/stm32f4/.vscode/README.md b/examples/stm32f4/.vscode/README.md new file mode 100644 index 000000000..4d20f43a8 --- /dev/null +++ b/examples/stm32f4/.vscode/README.md @@ -0,0 +1,109 @@ +# VS Code Configuration + +Example configurations for debugging programs in-editor with VS Code. +This directory contains configurations for two platforms: + + - `LM3S6965EVB` on QEMU + - `STM32F303x` via OpenOCD + +## Required Extensions + +If you have the `code` command in your path, you can run the following commands to install the necessary extensions. + +```sh +code --install-extension rust-lang.rust-analyzer +code --install-extension marus25.cortex-debug +``` + +Otherwise, you can use the Extensions view to search for and install them, or go directly to their marketplace pages and click the "Install" button. + +- [Rust Language Server (rust-analyzer)](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) +- [Cortex-Debug](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug) + +## Use + +The quickstart comes with two debug configurations. +Both are configured to build the project, using the default settings from `.cargo/config`, prior to starting a debug session. + +1. QEMU: Starts a debug session using an emulation of the `LM3S6965EVB` mcu. + - This works on a fresh `cargo generate` without modification of any of the settings described above. + - Semihosting output will be written to the Output view `Adapter Output`. + - `ITM` logging does not work with QEMU emulation. + +2. OpenOCD: Starts a debug session for a `STM32F3DISCOVERY` board (or any `STM32F303x` running at 8MHz). + - Follow the instructions above for configuring the build with `.cargo/config` and the `memory.x` linker script. + - `ITM` output will be written to the Output view `SWO: ITM [port: 0, type: console]` output. + +### Git + +Files in the `.vscode/` directory are `.gitignore`d by default because many files that may end up in the `.vscode/` directory should not be committed and shared. +If you would like to save this debug configuration to your repository and share it with your team, you'll need to explicitly `git add` the files to your repository. + +```sh +git add -f .vscode/launch.json +git add -f .vscode/tasks.json +git add -f .vscode/*.svd +``` + +## Customizing for other targets + +For full documentation, see the [Cortex-Debug][cortex-debug] repository. + +### Device + +Some configurations use this to automatically find the SVD file. +Replace this with the part number for your device. + +```json +"device": "STM32F303VCT6", +``` + +### OpenOCD Config Files + +The `configFiles` property specifies a list of files to pass to OpenOCD. + +```json +"configFiles": [ + "interface/stlink-v2-1.cfg", + "target/stm32f3x.cfg" +], +``` + +See the [OpenOCD config docs][openocd-config] for more information and the [OpenOCD repository for available configuration files][openocd-repo]. + +### SVD + +The SVD file is a standard way of describing all registers and peripherals of an ARM Cortex-M mCU. +Cortex-Debug needs this file to display the current register values for the peripherals on the device. + +You can probably find the SVD for your device on the vendor's website. + + +For example, the STM32F3DISCOVERY board uses an mcu from the `STM32F303x` line of processors. +All the SVD files for the STM32F3 series are available on [ST's Website][stm32f3]. +Download the [stm32f3 SVD pack][stm32f3-svd], and copy the `STM32F303.svd` file into `.vscode/`. +This line of the config tells the Cortex-Debug plug in where to find the file. + +```json +"svdFile": "${workspaceRoot}/.vscode/STM32F303.svd", +``` + +For other processors, simply copy the correct `*.svd` file into the project and update the config accordingly. + +### CPU Frequency + +If your device is running at a frequency other than 8MHz, you'll need to modify this line of `launch.json` for the `ITM` output to work correctly. + +```json +"cpuFrequency": 8000000, +``` + +### Other GDB Servers + +For information on setting up GDB servers other than OpenOCD, see the [Cortex-Debug repository][cortex-debug]. + +[cortex-debug]: https://github.com/Marus/cortex-debug +[stm32f3]: https://www.st.com/content/st_com/en/products/microcontrollers-microprocessors/stm32-32-bit-arm-cortex-mcus/stm32-mainstream-mcus/stm32f3-series.html#resource +[stm32f3-svd]: https://www.st.com/resource/en/svd/stm32f3_svd.zip +[openocd-config]: http://openocd.org/doc/html/Config-File-Guidelines.html +[openocd-repo]: https://sourceforge.net/p/openocd/code/ci/master/tree/tcl/ diff --git a/examples/stm32f4/.vscode/extensions.json b/examples/stm32f4/.vscode/extensions.json new file mode 100644 index 000000000..b7304974e --- /dev/null +++ b/examples/stm32f4/.vscode/extensions.json @@ -0,0 +1,17 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "rust-lang.rust-analyzer", + "marus25.cortex-debug", + "usernamehw.errorlens", + "tamasfe.even-better-toml", + "serayuzgur.crates" + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [ + + ] +} diff --git a/examples/stm32f4/.vscode/launch.json b/examples/stm32f4/.vscode/launch.json new file mode 100644 index 000000000..20cd4d2e8 --- /dev/null +++ b/examples/stm32f4/.vscode/launch.json @@ -0,0 +1,33 @@ +{ + /* + * Requires the Rust Language Server (rust-analyzer) and Cortex-Debug extensions + * https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer + * https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug + */ + "version": "0.2.0", + "configurations": [ + { + /* Configuration for the STM32F446 Discovery board */ + "type": "cortex-debug", + "request": "launch", + "name": "Debug (OpenOCD)", + "servertype": "openocd", + "cwd": "${workspaceRoot}", + "preLaunchTask": "Cargo Build (debug)", + "runToEntryPoint": "main", + "executable": "./target/thumbv7em-none-eabihf/debug/input_capture", + /* Run `cargo build --example itm` and uncomment this line to run itm example */ + // "executable": "./target/thumbv7em-none-eabihf/debug/examples/itm", + "device": "STM32F446RET6", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32f4x.cfg" + ], + "postLaunchCommands": [ + "monitor arm semihosting enable" + ], + "postRestartCommands": [], + "postResetCommands": [], + } + ] +} \ No newline at end of file diff --git a/examples/stm32f4/.vscode/tasks.json b/examples/stm32f4/.vscode/tasks.json new file mode 100644 index 000000000..9109a6157 --- /dev/null +++ b/examples/stm32f4/.vscode/tasks.json @@ -0,0 +1,43 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + /* + * This is the default cargo build task, + * but we need to provide a label for it, + * so we can invoke it from the debug launcher. + */ + "label": "Cargo Build (debug)", + "type": "process", + "command": "cargo", + "args": ["build", "--bin", "input_capture"], + "problemMatcher": [ + "$rustc" + ], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "Cargo Build (release)", + "type": "process", + "command": "cargo", + "args": ["build", "--release"], + "problemMatcher": [ + "$rustc" + ], + "group": "build" + }, + { + "label": "Cargo Clean", + "type": "process", + "command": "cargo", + "args": ["clean"], + "problemMatcher": [], + "group": "build" + }, + ] +} diff --git a/examples/stm32f4/openocd.cfg b/examples/stm32f4/openocd.cfg new file mode 100644 index 000000000..e41d52b1a --- /dev/null +++ b/examples/stm32f4/openocd.cfg @@ -0,0 +1,5 @@ +# Sample OpenOCD configuration for the STM32F3DISCOVERY development board + +source [find interface/stlink.cfg] + +source [find target/stm32f4x.cfg] diff --git a/examples/stm32f4/openocd.gdb b/examples/stm32f4/openocd.gdb new file mode 100644 index 000000000..7795319fb --- /dev/null +++ b/examples/stm32f4/openocd.gdb @@ -0,0 +1,40 @@ +target extended-remote :3333 + +# print demangled symbols +set print asm-demangle on + +# set backtrace limit to not have infinite backtrace loops +set backtrace limit 32 + +# detect unhandled exceptions, hard faults and panics +break DefaultHandler +break HardFault +break rust_begin_unwind +# # run the next few lines so the panic message is printed immediately +# # the number needs to be adjusted for your panic handler +# commands $bpnum +# next 4 +# end + +# *try* to stop at the user entry point (it might be gone due to inlining) +break main + +monitor arm semihosting enable + +# # send captured ITM to the file itm.fifo +# # (the microcontroller SWO pin must be connected to the programmer SWO pin) +# # 8000000 must match the core clock frequency +# monitor tpiu config internal itm.txt uart off 8000000 + +# # OR: make the microcontroller SWO pin output compatible with UART (8N1) +# # 8000000 must match the core clock frequency +# # 2000000 is the frequency of the SWO pin +# monitor tpiu config external uart off 8000000 2000000 + +# # enable ITM port 0 +# monitor itm port 0 on + +load + +# start the process but immediately halt the processor +stepi diff --git a/examples/stm32f4/src/bin/input_capture.rs b/examples/stm32f4/src/bin/input_capture.rs new file mode 100644 index 000000000..202f363fc --- /dev/null +++ b/examples/stm32f4/src/bin/input_capture.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::{ + gpio::{self, Level, Output, Speed}, + time::Hertz, +}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +use embassy_stm32::timer::{ + input_capture::{CapturePin, InputCapture}, + Channel, +}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut led = Output::new(p.PB2, Level::High, Speed::Low); + + let ic = CapturePin::new_ch3(p.PB10, gpio::Pull::None); + let drv = InputCapture::new(p.TIM2, None, None, Some(ic), None, Hertz::mhz(1), Default::default()); + let mut _last: u32; + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + _last = drv.get_capture_value(Channel::Ch1); + } +}