Compare commits
	
		
			14 Commits
		
	
	
		
			e-h-intern
			...
			doc-bind-i
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 6c746dcf39 | ||
|  | 3ef18ec133 | ||
|  | 4e7b855b2d | ||
|  | 3f30e87cb5 | ||
|  | a9ec623622 | ||
|  | fe218ed978 | ||
|  | c27459c052 | ||
|  | 881cb16d28 | ||
|  | c05149e5e4 | ||
|  | 8fa5a6b282 | ||
|  | 1f6ffdcdd2 | ||
|  | bf7115cb44 | ||
|  | 0b0ca62a95 | ||
|  | 35f16c6003 | 
| @@ -2,6 +2,7 @@ | |||||||
| ** xref:basic_application.adoc[Basic application] | ** xref:basic_application.adoc[Basic application] | ||||||
| ** xref:project_structure.adoc[Project Structure] | ** xref:project_structure.adoc[Project Structure] | ||||||
| ** xref:new_project.adoc[Starting a new Embassy project] | ** xref:new_project.adoc[Starting a new Embassy project] | ||||||
|  | ** xref:best_practices.adoc[Best Practices] | ||||||
| * xref:layer_by_layer.adoc[Bare metal to async] | * xref:layer_by_layer.adoc[Bare metal to async] | ||||||
| * xref:runtime.adoc[Executor] | * xref:runtime.adoc[Executor] | ||||||
| * xref:delaying_a_task.adoc[Delaying a Task] | * xref:delaying_a_task.adoc[Delaying a Task] | ||||||
| @@ -11,7 +12,7 @@ | |||||||
| * xref:bootloader.adoc[Bootloader] | * xref:bootloader.adoc[Bootloader] | ||||||
|  |  | ||||||
| * xref:examples.adoc[Examples] | * xref:examples.adoc[Examples] | ||||||
| * xref:developer.adoc[Developer] | * xref:developer.adoc[Developer Docs] | ||||||
| ** xref:developer_stm32.adoc[Developer: STM32] | ** xref:developer_stm32.adoc[Developer Docs: STM32] | ||||||
| * xref:embassy_in_the_wild.adoc[Embassy in the wild] | * xref:embassy_in_the_wild.adoc[Embassy in the wild] | ||||||
| * xref:faq.adoc[Frequently Asked Questions] | * xref:faq.adoc[Frequently Asked Questions] | ||||||
|   | |||||||
							
								
								
									
										53
									
								
								docs/modules/ROOT/pages/best_practices.adoc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								docs/modules/ROOT/pages/best_practices.adoc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | = Best Practices | ||||||
|  |  | ||||||
|  | Over time, a couple of best practices have emerged. The following list should serve as a guideline for developers writing embedded software in _Rust_, especially in the context of the _Embassy_ framework. | ||||||
|  |  | ||||||
|  | == Passing Buffers by Reference | ||||||
|  | It may be tempting to pass arrays or wrappers, like link:https://docs.rs/heapless/latest/heapless/[`heapless::Vec`], to a function or return one just like you would with a `std::Vec`. However, in most embedded applications you don't want to spend ressources on an allocator and end up placing buffers on the stack. | ||||||
|  | This, however, can easily blow up your stack if you are not careful. | ||||||
|  |  | ||||||
|  | Consider the following example: | ||||||
|  | [,rust] | ||||||
|  | ---- | ||||||
|  | fn process_buffer(mut buf: [u8; 1024]) -> [u8; 1024] { | ||||||
|  |     // do stuff and return new buffer | ||||||
|  |     for elem in buf.iter_mut() { | ||||||
|  |         *elem = 0; | ||||||
|  |     } | ||||||
|  |     buf | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn main() -> () { | ||||||
|  |     let buf = [1u8; 1024]; | ||||||
|  |     let buf_new = process_buffer(buf); | ||||||
|  |     // do stuff with buf_new | ||||||
|  |     () | ||||||
|  | } | ||||||
|  | ---- | ||||||
|  | When calling `process_buffer` in your program, a copy of the buffer you pass to the function will be created, | ||||||
|  | consuming another 1024 bytes. | ||||||
|  | After the processing, another 1024 byte buffer will be placed on the stack to be returned to the caller. | ||||||
|  | (You can check the assembly, there will be two memcopy operations, e.g., `bl __aeabi_memcpy` when compiling for a Cortex-M processor.) | ||||||
|  |  | ||||||
|  | *Possible Solution:* | ||||||
|  |  | ||||||
|  | Pass the data by reference and not by value on both, the way in and the way out. | ||||||
|  | For example, you could return a slice of the input buffer as the output. | ||||||
|  | Requiring the lifetime of the input slice and the output slice to be the same, the memory safetly of this procedure will be enforced by the compiler. | ||||||
|  |  | ||||||
|  | [,rust] | ||||||
|  | ---- | ||||||
|  | fn process_buffer<'a>(buf: &'a mut [u8]) -> &'a mut[u8] { | ||||||
|  |     for elem in buf.iter_mut() { | ||||||
|  |         *elem = 0; | ||||||
|  |     } | ||||||
|  |     buf | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn main() -> () { | ||||||
|  |     let mut buf = [1u8; 1024]; | ||||||
|  |     let buf_new = process_buffer(&mut buf); | ||||||
|  |     // do stuff with buf_new | ||||||
|  |     () | ||||||
|  | } | ||||||
|  | ---- | ||||||
| @@ -47,7 +47,8 @@ The first step to managing your binary size is to set up your link:https://doc.r | |||||||
| debug = false | debug = false | ||||||
| lto = true | lto = true | ||||||
| opt-level = "s" | opt-level = "s" | ||||||
| incremental = true | incremental = false | ||||||
|  | codegen-units = 1 | ||||||
| ---- | ---- | ||||||
|  |  | ||||||
| All of these flags are elaborated on in the Rust Book page linked above. | All of these flags are elaborated on in the Rust Book page linked above. | ||||||
| @@ -135,3 +136,20 @@ embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "e5fdd | |||||||
| ---- | ---- | ||||||
|  |  | ||||||
| Note that the git revision should match any other embassy patches or git dependencies that you are using! | Note that the git revision should match any other embassy patches or git dependencies that you are using! | ||||||
|  |  | ||||||
|  | == How can I optimize the speed of my embassy-stm32 program? | ||||||
|  |  | ||||||
|  | * Make sure RCC is set up to go as fast as possible | ||||||
|  | * Make sure link:https://docs.rs/cortex-m/latest/cortex_m/peripheral/struct.SCB.html[flash cache] is enabled | ||||||
|  | * build with `--release` | ||||||
|  | * Set the following keys for the release profile in your `Cargo.toml`: | ||||||
|  |     ** `opt-level = "s"` | ||||||
|  |     ** `lto = "fat"` | ||||||
|  | * Set the following keys in the `[unstable]` section of your `.cargo/config.toml` | ||||||
|  |     ** `build-std = ["core"]` | ||||||
|  |     ** `build-std-features = ["panic_immediate_abort"]` | ||||||
|  | * Enable feature `embassy-time/generic-queue`, disable feature `embassy-executor/integrated-timers` | ||||||
|  | * When using `InterruptExecutor`: | ||||||
|  |     ** disable `executor-thread` | ||||||
|  |     ** make `main`` spawn everything, then enable link:https://docs.rs/cortex-m/latest/cortex_m/peripheral/struct.SCB.html#method.set_sleeponexit[SCB.SLEEPONEXIT] and `loop { cortex_m::asm::wfi() }` | ||||||
|  |     ** *Note:*  If you need 2 priority levels, using 2 interrupt executors is better than 1 thread executor + 1 interrupt executor. | ||||||
| @@ -97,6 +97,28 @@ mod chip; | |||||||
| /// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) | /// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) | ||||||
| /// and implements the right [`Binding`]s for it. You can pass this struct to drivers to | /// and implements the right [`Binding`]s for it. You can pass this struct to drivers to | ||||||
| /// prove at compile-time that the right interrupts have been bound. | /// prove at compile-time that the right interrupts have been bound. | ||||||
|  | /// | ||||||
|  | /// Example of how to bind one interrupt: | ||||||
|  | /// | ||||||
|  | /// ```rust,ignore | ||||||
|  | /// use embassy_nrf::{bind_interrupts, spim, peripherals}; | ||||||
|  | /// | ||||||
|  | /// bind_interrupts!(struct Irqs { | ||||||
|  | ///     SPIM3 => spim::InterruptHandler<peripherals::SPI3>; | ||||||
|  | /// }); | ||||||
|  | /// ``` | ||||||
|  | /// | ||||||
|  | /// Example of how to bind multiple interrupts in a single macro invocation: | ||||||
|  | /// | ||||||
|  | /// ```rust,ignore | ||||||
|  | /// use embassy_nrf::{bind_interrupts, spim, twim, peripherals}; | ||||||
|  | /// | ||||||
|  | /// bind_interrupts!(struct Irqs { | ||||||
|  | ///     SPIM3 => spim::InterruptHandler<peripherals::SPI3>; | ||||||
|  | ///     SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 => twim::InterruptHandler<peripherals::TWISPI0>; | ||||||
|  | /// }); | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
| // developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. | // developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. | ||||||
| #[macro_export] | #[macro_export] | ||||||
| macro_rules! bind_interrupts { | macro_rules! bind_interrupts { | ||||||
|   | |||||||
| @@ -86,6 +86,17 @@ embassy_hal_internal::interrupt_mod!( | |||||||
| /// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) | /// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) | ||||||
| /// and implements the right [`Binding`]s for it. You can pass this struct to drivers to | /// and implements the right [`Binding`]s for it. You can pass this struct to drivers to | ||||||
| /// prove at compile-time that the right interrupts have been bound. | /// prove at compile-time that the right interrupts have been bound. | ||||||
|  | /// | ||||||
|  | /// Example of how to bind one interrupt: | ||||||
|  | /// | ||||||
|  | /// ```rust,ignore | ||||||
|  | /// use embassy_rp::{bind_interrupts, usb, peripherals}; | ||||||
|  | /// | ||||||
|  | /// bind_interrupts!(struct Irqs { | ||||||
|  | ///     USBCTRL_IRQ => usb::InterruptHandler<peripherals::USB>; | ||||||
|  | /// }); | ||||||
|  | /// ``` | ||||||
|  | /// | ||||||
| // developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. | // developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. | ||||||
| #[macro_export] | #[macro_export] | ||||||
| macro_rules! bind_interrupts { | macro_rules! bind_interrupts { | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ rand_core = "0.6.3" | |||||||
| sdio-host = "0.5.0" | sdio-host = "0.5.0" | ||||||
| embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true } | embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true } | ||||||
| critical-section = "1.1" | critical-section = "1.1" | ||||||
| stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-7117ad49c06fa00c388130a34977e029910083bd" } | stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-019a5da1c47c092c199bc39a7f84fb444f2adcdf" } | ||||||
| vcell = "0.1.3" | vcell = "0.1.3" | ||||||
| bxcan = "0.7.0" | bxcan = "0.7.0" | ||||||
| nb = "1.0.0" | nb = "1.0.0" | ||||||
| @@ -76,7 +76,7 @@ critical-section = { version = "1.1", features = ["std"] } | |||||||
| [build-dependencies] | [build-dependencies] | ||||||
| proc-macro2 = "1.0.36" | proc-macro2 = "1.0.36" | ||||||
| quote = "1.0.15" | quote = "1.0.15" | ||||||
| stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-7117ad49c06fa00c388130a34977e029910083bd", default-features = false, features = ["metadata"]} | stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-019a5da1c47c092c199bc39a7f84fb444f2adcdf", default-features = false, features = ["metadata"]} | ||||||
|  |  | ||||||
|  |  | ||||||
| [features] | [features] | ||||||
|   | |||||||
| @@ -930,6 +930,10 @@ fn main() { | |||||||
|                     } else if pin.signal.starts_with("INN") { |                     } else if pin.signal.starts_with("INN") { | ||||||
|                         // TODO handle in the future when embassy supports differential measurements |                         // TODO handle in the future when embassy supports differential measurements | ||||||
|                         None |                         None | ||||||
|  |                     } else if pin.signal.starts_with("IN") && pin.signal.ends_with("b") { | ||||||
|  |                         // we number STM32L1 ADC bank 1 as 0..=31, bank 2 as 32..=63 | ||||||
|  |                         let signal = pin.signal.strip_prefix("IN").unwrap().strip_suffix("b").unwrap(); | ||||||
|  |                         Some(32u8 + signal.parse::<u8>().unwrap()) | ||||||
|                     } else if pin.signal.starts_with("IN") { |                     } else if pin.signal.starts_with("IN") { | ||||||
|                         Some(pin.signal.strip_prefix("IN").unwrap().parse().unwrap()) |                         Some(pin.signal.strip_prefix("IN").unwrap().parse().unwrap()) | ||||||
|                     } else { |                     } else { | ||||||
|   | |||||||
| @@ -148,7 +148,7 @@ impl<'d, T: Instance> Adc<'d, T> { | |||||||
|             reg.set_cont(false); |             reg.set_cont(false); | ||||||
|             reg.set_exttrig(true); |             reg.set_exttrig(true); | ||||||
|             reg.set_swstart(false); |             reg.set_swstart(false); | ||||||
|             reg.set_extsel(crate::pac::adc::vals::Extsel::SWSTART); |             reg.set_extsel(7); // SWSTART | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         // Configure the channel to sample |         // Configure the channel to sample | ||||||
|   | |||||||
							
								
								
									
										413
									
								
								embassy-stm32/src/adc/f3_v1_1.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										413
									
								
								embassy-stm32/src/adc/f3_v1_1.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,413 @@ | |||||||
|  | use core::future::poll_fn; | ||||||
|  | use core::marker::PhantomData; | ||||||
|  | use core::task::Poll; | ||||||
|  |  | ||||||
|  | use embassy_futures::yield_now; | ||||||
|  | use embassy_hal_internal::into_ref; | ||||||
|  | use embassy_time::Instant; | ||||||
|  |  | ||||||
|  | use super::Resolution; | ||||||
|  | use crate::adc::{Adc, AdcPin, Instance, SampleTime}; | ||||||
|  | use crate::interrupt::typelevel::Interrupt; | ||||||
|  | use crate::time::Hertz; | ||||||
|  | use crate::{interrupt, Peripheral}; | ||||||
|  |  | ||||||
|  | const ADC_FREQ: Hertz = crate::rcc::HSI_FREQ; | ||||||
|  |  | ||||||
|  | pub const VDDA_CALIB_MV: u32 = 3300; | ||||||
|  | pub const ADC_MAX: u32 = (1 << 12) - 1; | ||||||
|  | pub const VREF_INT: u32 = 1230; | ||||||
|  |  | ||||||
|  | pub enum AdcPowerMode { | ||||||
|  |     AlwaysOn, | ||||||
|  |     DelayOff, | ||||||
|  |     IdleOff, | ||||||
|  |     DelayIdleOff, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub enum Prescaler { | ||||||
|  |     Div1, | ||||||
|  |     Div2, | ||||||
|  |     Div3, | ||||||
|  |     Div4, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Interrupt handler. | ||||||
|  | pub struct InterruptHandler<T: Instance> { | ||||||
|  |     _phantom: PhantomData<T>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> { | ||||||
|  |     unsafe fn on_interrupt() { | ||||||
|  |         if T::regs().sr().read().eoc() { | ||||||
|  |             T::regs().cr1().modify(|w| w.set_eocie(false)); | ||||||
|  |         } else { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         T::state().waker.wake(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn update_vref<T: Instance>(op: i8) { | ||||||
|  |     static VREF_STATUS: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0); | ||||||
|  |  | ||||||
|  |     if op > 0 { | ||||||
|  |         if VREF_STATUS.fetch_add(1, core::sync::atomic::Ordering::SeqCst) == 0 { | ||||||
|  |             T::regs().ccr().modify(|w| w.set_tsvrefe(true)); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         if VREF_STATUS.fetch_sub(1, core::sync::atomic::Ordering::SeqCst) == 1 { | ||||||
|  |             T::regs().ccr().modify(|w| w.set_tsvrefe(false)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct Vref<T: Instance>(core::marker::PhantomData<T>); | ||||||
|  | impl<T: Instance> AdcPin<T> for Vref<T> {} | ||||||
|  | impl<T: Instance> super::sealed::AdcPin<T> for Vref<T> { | ||||||
|  |     fn channel(&self) -> u8 { | ||||||
|  |         17 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<T: Instance> Vref<T> { | ||||||
|  |     /// The value that vref would be if vdda was at 3000mv | ||||||
|  |     pub fn calibrated_value(&self) -> u16 { | ||||||
|  |         crate::pac::VREFINTCAL.data().read().value() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub async fn calibrate(&mut self, adc: &mut Adc<'_, T>) -> Calibration { | ||||||
|  |         let vref_val = adc.read(self).await; | ||||||
|  |         Calibration { | ||||||
|  |             vref_cal: self.calibrated_value(), | ||||||
|  |             vref_val, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct Calibration { | ||||||
|  |     vref_cal: u16, | ||||||
|  |     vref_val: u16, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Calibration { | ||||||
|  |     /// The millivolts that the calibration value was measured at | ||||||
|  |     pub const CALIBRATION_UV: u32 = 3_000_000; | ||||||
|  |  | ||||||
|  |     /// Returns the measured VddA in microvolts (uV) | ||||||
|  |     pub fn vdda_uv(&self) -> u32 { | ||||||
|  |         (Self::CALIBRATION_UV * self.vref_cal as u32) / self.vref_val as u32 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Returns the measured VddA as an f32 | ||||||
|  |     pub fn vdda_f32(&self) -> f32 { | ||||||
|  |         (Self::CALIBRATION_UV as f32 / 1_000.0) * (self.vref_cal as f32 / self.vref_val as f32) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Returns a calibrated voltage value as in microvolts (uV) | ||||||
|  |     pub fn cal_uv(&self, raw: u16, resolution: super::Resolution) -> u32 { | ||||||
|  |         (self.vdda_uv() / resolution.to_max_count()) * raw as u32 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Returns a calibrated voltage value as an f32 | ||||||
|  |     pub fn cal_f32(&self, raw: u16, resolution: super::Resolution) -> f32 { | ||||||
|  |         raw as f32 * self.vdda_f32() / resolution.to_max_count() as f32 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<T: Instance> Drop for Vref<T> { | ||||||
|  |     fn drop(&mut self) { | ||||||
|  |         update_vref::<T>(-1) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct Temperature<T: Instance>(core::marker::PhantomData<T>); | ||||||
|  | impl<T: Instance> AdcPin<T> for Temperature<T> {} | ||||||
|  | impl<T: Instance> super::sealed::AdcPin<T> for Temperature<T> { | ||||||
|  |     fn channel(&self) -> u8 { | ||||||
|  |         16 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<T: Instance> Drop for Temperature<T> { | ||||||
|  |     fn drop(&mut self) { | ||||||
|  |         update_vref::<T>(-1) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'d, T: Instance> Adc<'d, T> { | ||||||
|  |     pub fn new( | ||||||
|  |         adc: impl Peripheral<P = T> + 'd, | ||||||
|  |         _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd, | ||||||
|  |     ) -> Self { | ||||||
|  |         into_ref!(adc); | ||||||
|  |  | ||||||
|  |         T::enable_and_reset(); | ||||||
|  |  | ||||||
|  |         //let r = T::regs(); | ||||||
|  |         //r.cr2().write(|w| w.set_align(true)); | ||||||
|  |  | ||||||
|  |         T::Interrupt::unpend(); | ||||||
|  |         unsafe { | ||||||
|  |             T::Interrupt::enable(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         Self { adc } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn freq() -> Hertz { | ||||||
|  |         let div = T::regs().ccr().read().adcpre() + 1; | ||||||
|  |         ADC_FREQ / div as u32 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub async fn set_resolution(&mut self, res: Resolution) { | ||||||
|  |         let was_on = Self::is_on(); | ||||||
|  |         if was_on { | ||||||
|  |             self.stop_adc().await; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         T::regs().cr1().modify(|w| w.set_res(res.into())); | ||||||
|  |  | ||||||
|  |         if was_on { | ||||||
|  |             self.start_adc().await; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn resolution(&self) -> Resolution { | ||||||
|  |         match T::regs().cr1().read().res() { | ||||||
|  |             crate::pac::adc::vals::Res::TWELVEBIT => Resolution::TwelveBit, | ||||||
|  |             crate::pac::adc::vals::Res::TENBIT => Resolution::TenBit, | ||||||
|  |             crate::pac::adc::vals::Res::EIGHTBIT => Resolution::EightBit, | ||||||
|  |             crate::pac::adc::vals::Res::SIXBIT => Resolution::SixBit, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn enable_vref(&self) -> Vref<T> { | ||||||
|  |         update_vref::<T>(1); | ||||||
|  |  | ||||||
|  |         Vref(core::marker::PhantomData) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn enable_temperature(&self) -> Temperature<T> { | ||||||
|  |         T::regs().ccr().modify(|w| w.set_tsvrefe(true)); | ||||||
|  |  | ||||||
|  |         Temperature::<T>(core::marker::PhantomData) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Perform a single conversion. | ||||||
|  |     async fn convert(&mut self) -> u16 { | ||||||
|  |         let was_on = Self::is_on(); | ||||||
|  |  | ||||||
|  |         if !was_on { | ||||||
|  |             self.start_adc().await; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         self.wait_sample_ready().await; | ||||||
|  |  | ||||||
|  |         T::regs().sr().write(|_| {}); | ||||||
|  |         T::regs().cr1().modify(|w| { | ||||||
|  |             w.set_eocie(true); | ||||||
|  |             w.set_scan(false); | ||||||
|  |         }); | ||||||
|  |         T::regs().cr2().modify(|w| { | ||||||
|  |             w.set_swstart(true); | ||||||
|  |             w.set_cont(false); | ||||||
|  |         }); // swstart cleared by HW | ||||||
|  |  | ||||||
|  |         let res = poll_fn(|cx| { | ||||||
|  |             T::state().waker.register(cx.waker()); | ||||||
|  |  | ||||||
|  |             if T::regs().sr().read().eoc() { | ||||||
|  |                 let res = T::regs().dr().read().rdata(); | ||||||
|  |                 Poll::Ready(res) | ||||||
|  |             } else { | ||||||
|  |                 Poll::Pending | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |         .await; | ||||||
|  |  | ||||||
|  |         if !was_on { | ||||||
|  |             self.stop_adc().await; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         res | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[inline(always)] | ||||||
|  |     fn is_on() -> bool { | ||||||
|  |         T::regs().sr().read().adons() || T::regs().cr2().read().adon() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub async fn start_adc(&self) { | ||||||
|  |         //defmt::trace!("Turn ADC on"); | ||||||
|  |         T::regs().cr2().modify(|w| w.set_adon(true)); | ||||||
|  |         //defmt::trace!("Waiting for ADC to turn on"); | ||||||
|  |  | ||||||
|  |         let mut t = Instant::now(); | ||||||
|  |  | ||||||
|  |         while !T::regs().sr().read().adons() { | ||||||
|  |             yield_now().await; | ||||||
|  |             if t.elapsed() > embassy_time::Duration::from_millis(1000) { | ||||||
|  |                 t = Instant::now(); | ||||||
|  |                 //defmt::trace!("ADC still not on"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         //defmt::trace!("ADC on"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub async fn stop_adc(&self) { | ||||||
|  |         if T::regs().cr2().read().adon() { | ||||||
|  |             //defmt::trace!("ADC should be on, wait for it to start"); | ||||||
|  |             while !T::regs().csr().read().adons1() { | ||||||
|  |                 yield_now().await; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         //defmt::trace!("Turn ADC off"); | ||||||
|  |  | ||||||
|  |         T::regs().cr2().modify(|w| w.set_adon(false)); | ||||||
|  |  | ||||||
|  |         //defmt::trace!("Waiting for ADC to turn off"); | ||||||
|  |  | ||||||
|  |         while T::regs().csr().read().adons1() { | ||||||
|  |             yield_now().await; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub async fn read(&mut self, pin: &mut impl AdcPin<T>) -> u16 { | ||||||
|  |         self.set_sample_sequence(&[pin.channel()]).await; | ||||||
|  |         self.convert().await | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async fn wait_sample_ready(&self) { | ||||||
|  |         //trace!("Waiting for sample channel to be ready"); | ||||||
|  |         while T::regs().sr().read().rcnr() { | ||||||
|  |             yield_now().await; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub async fn set_sample_time(&mut self, pin: &mut impl AdcPin<T>, sample_time: SampleTime) { | ||||||
|  |         if Self::get_channel_sample_time(pin.channel()) != sample_time { | ||||||
|  |             self.stop_adc().await; | ||||||
|  |             unsafe { | ||||||
|  |                 Self::set_channel_sample_time(pin.channel(), sample_time); | ||||||
|  |             } | ||||||
|  |             self.start_adc().await; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn get_sample_time(&self, pin: &impl AdcPin<T>) -> SampleTime { | ||||||
|  |         Self::get_channel_sample_time(pin.channel()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Sets the channel sample time | ||||||
|  |     /// | ||||||
|  |     /// ## SAFETY: | ||||||
|  |     /// - ADON == 0 i.e ADC must not be enabled when this is called. | ||||||
|  |     unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { | ||||||
|  |         let sample_time = sample_time.into(); | ||||||
|  |  | ||||||
|  |         match ch { | ||||||
|  |             0..=9 => T::regs().smpr3().modify(|reg| reg.set_smp(ch as _, sample_time)), | ||||||
|  |             10..=19 => T::regs() | ||||||
|  |                 .smpr2() | ||||||
|  |                 .modify(|reg| reg.set_smp(ch as usize - 10, sample_time)), | ||||||
|  |             20..=29 => T::regs() | ||||||
|  |                 .smpr1() | ||||||
|  |                 .modify(|reg| reg.set_smp(ch as usize - 20, sample_time)), | ||||||
|  |             30..=31 => T::regs() | ||||||
|  |                 .smpr0() | ||||||
|  |                 .modify(|reg| reg.set_smp(ch as usize - 30, sample_time)), | ||||||
|  |             _ => panic!("Invalid channel to sample"), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn get_channel_sample_time(ch: u8) -> SampleTime { | ||||||
|  |         match ch { | ||||||
|  |             0..=9 => T::regs().smpr3().read().smp(ch as _), | ||||||
|  |             10..=19 => T::regs().smpr2().read().smp(ch as usize - 10), | ||||||
|  |             20..=29 => T::regs().smpr1().read().smp(ch as usize - 20), | ||||||
|  |             30..=31 => T::regs().smpr0().read().smp(ch as usize - 30), | ||||||
|  |             _ => panic!("Invalid channel to sample"), | ||||||
|  |         } | ||||||
|  |         .into() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Sets the sequence to sample the ADC. Must be less than 28 elements. | ||||||
|  |     async fn set_sample_sequence(&self, sequence: &[u8]) { | ||||||
|  |         assert!(sequence.len() <= 28); | ||||||
|  |         let mut iter = sequence.iter(); | ||||||
|  |         T::regs().sqr1().modify(|w| w.set_l((sequence.len() - 1) as _)); | ||||||
|  |         for (idx, ch) in iter.by_ref().take(6).enumerate() { | ||||||
|  |             T::regs().sqr5().modify(|w| w.set_sq(idx, *ch)); | ||||||
|  |         } | ||||||
|  |         for (idx, ch) in iter.by_ref().take(6).enumerate() { | ||||||
|  |             T::regs().sqr4().modify(|w| w.set_sq(idx, *ch)); | ||||||
|  |         } | ||||||
|  |         for (idx, ch) in iter.by_ref().take(6).enumerate() { | ||||||
|  |             T::regs().sqr3().modify(|w| w.set_sq(idx, *ch)); | ||||||
|  |         } | ||||||
|  |         for (idx, ch) in iter.by_ref().take(6).enumerate() { | ||||||
|  |             T::regs().sqr2().modify(|w| w.set_sq(idx, *ch)); | ||||||
|  |         } | ||||||
|  |         for (idx, ch) in iter.by_ref().take(4).enumerate() { | ||||||
|  |             T::regs().sqr1().modify(|w| w.set_sq(idx, *ch)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn get_res_clks(res: Resolution) -> u32 { | ||||||
|  |         match res { | ||||||
|  |             Resolution::TwelveBit => 12, | ||||||
|  |             Resolution::TenBit => 11, | ||||||
|  |             Resolution::EightBit => 9, | ||||||
|  |             Resolution::SixBit => 7, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn get_sample_time_clks(sample_time: SampleTime) -> u32 { | ||||||
|  |         match sample_time { | ||||||
|  |             SampleTime::Cycles4 => 4, | ||||||
|  |             SampleTime::Cycles9 => 9, | ||||||
|  |             SampleTime::Cycles16 => 16, | ||||||
|  |             SampleTime::Cycles24 => 24, | ||||||
|  |             SampleTime::Cycles48 => 48, | ||||||
|  |             SampleTime::Cycles96 => 96, | ||||||
|  |             SampleTime::Cycles192 => 192, | ||||||
|  |             SampleTime::Cycles384 => 384, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn sample_time_for_us(&self, us: u32) -> SampleTime { | ||||||
|  |         let res_clks = Self::get_res_clks(self.resolution()); | ||||||
|  |         let us_clks = us * Self::freq().0 / 1_000_000; | ||||||
|  |         let clks = us_clks.saturating_sub(res_clks); | ||||||
|  |         match clks { | ||||||
|  |             0..=4 => SampleTime::Cycles4, | ||||||
|  |             5..=9 => SampleTime::Cycles9, | ||||||
|  |             10..=16 => SampleTime::Cycles16, | ||||||
|  |             17..=24 => SampleTime::Cycles24, | ||||||
|  |             25..=48 => SampleTime::Cycles48, | ||||||
|  |             49..=96 => SampleTime::Cycles96, | ||||||
|  |             97..=192 => SampleTime::Cycles192, | ||||||
|  |             193.. => SampleTime::Cycles384, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn us_for_cfg(&self, res: Resolution, sample_time: SampleTime) -> u32 { | ||||||
|  |         let res_clks = Self::get_res_clks(res); | ||||||
|  |         let sample_clks = Self::get_sample_time_clks(sample_time); | ||||||
|  |         (res_clks + sample_clks) * 1_000_000 / Self::freq().0 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'d, T: Instance> Drop for Adc<'d, T> { | ||||||
|  |     fn drop(&mut self) { | ||||||
|  |         while !T::regs().sr().read().adons() {} | ||||||
|  |  | ||||||
|  |         T::regs().cr2().modify(|w| w.set_adon(false)); | ||||||
|  |  | ||||||
|  |         T::disable(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -3,6 +3,7 @@ | |||||||
| #[cfg(not(adc_f3_v2))] | #[cfg(not(adc_f3_v2))] | ||||||
| #[cfg_attr(adc_f1, path = "f1.rs")] | #[cfg_attr(adc_f1, path = "f1.rs")] | ||||||
| #[cfg_attr(adc_f3, path = "f3.rs")] | #[cfg_attr(adc_f3, path = "f3.rs")] | ||||||
|  | #[cfg_attr(adc_f3_v1_1, path = "f3_v1_1.rs")] | ||||||
| #[cfg_attr(adc_v1, path = "v1.rs")] | #[cfg_attr(adc_v1, path = "v1.rs")] | ||||||
| #[cfg_attr(adc_v2, path = "v2.rs")] | #[cfg_attr(adc_v2, path = "v2.rs")] | ||||||
| #[cfg_attr(any(adc_v3, adc_g0), path = "v3.rs")] | #[cfg_attr(any(adc_v3, adc_g0), path = "v3.rs")] | ||||||
| @@ -26,20 +27,20 @@ use crate::peripherals; | |||||||
| pub struct Adc<'d, T: Instance> { | pub struct Adc<'d, T: Instance> { | ||||||
|     #[allow(unused)] |     #[allow(unused)] | ||||||
|     adc: crate::PeripheralRef<'d, T>, |     adc: crate::PeripheralRef<'d, T>, | ||||||
|     #[cfg(not(adc_f3_v2))] |     #[cfg(not(any(adc_f3_v2, adc_f3_v1_1)))] | ||||||
|     sample_time: SampleTime, |     sample_time: SampleTime, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub(crate) mod sealed { | pub(crate) mod sealed { | ||||||
|     #[cfg(any(adc_f1, adc_f3, adc_v1))] |     #[cfg(any(adc_f1, adc_f3, adc_v1, adc_f3_v1_1))] | ||||||
|     use embassy_sync::waitqueue::AtomicWaker; |     use embassy_sync::waitqueue::AtomicWaker; | ||||||
|  |  | ||||||
|     #[cfg(any(adc_f1, adc_f3, adc_v1))] |     #[cfg(any(adc_f1, adc_f3, adc_v1, adc_f3_v1_1))] | ||||||
|     pub struct State { |     pub struct State { | ||||||
|         pub waker: AtomicWaker, |         pub waker: AtomicWaker, | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[cfg(any(adc_f1, adc_f3, adc_v1))] |     #[cfg(any(adc_f1, adc_f3, adc_v1, adc_f3_v1_1))] | ||||||
|     impl State { |     impl State { | ||||||
|         pub const fn new() -> Self { |         pub const fn new() -> Self { | ||||||
|             Self { |             Self { | ||||||
| @@ -54,11 +55,11 @@ pub(crate) mod sealed { | |||||||
|  |  | ||||||
|     pub trait Instance: InterruptableInstance { |     pub trait Instance: InterruptableInstance { | ||||||
|         fn regs() -> crate::pac::adc::Adc; |         fn regs() -> crate::pac::adc::Adc; | ||||||
|         #[cfg(not(any(adc_f1, adc_v1, adc_f3_v2, adc_g0)))] |         #[cfg(not(any(adc_f1, adc_v1, adc_f3_v2, adc_f3_v1_1, adc_g0)))] | ||||||
|         fn common_regs() -> crate::pac::adccommon::AdcCommon; |         fn common_regs() -> crate::pac::adccommon::AdcCommon; | ||||||
|         #[cfg(adc_f3)] |         #[cfg(adc_f3)] | ||||||
|         fn frequency() -> crate::time::Hertz; |         fn frequency() -> crate::time::Hertz; | ||||||
|         #[cfg(any(adc_f1, adc_f3, adc_v1))] |         #[cfg(any(adc_f1, adc_f3, adc_v1, adc_f3_v1_1))] | ||||||
|         fn state() -> &'static State; |         fn state() -> &'static State; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -74,9 +75,9 @@ pub(crate) mod sealed { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(not(any(adc_f1, adc_v1, adc_v2, adc_v3, adc_v4, adc_f3, adc_g0)))] | #[cfg(not(any(adc_f1, adc_v1, adc_v2, adc_v3, adc_v4, adc_f3, adc_f3_v1_1, adc_g0)))] | ||||||
| pub trait Instance: sealed::Instance + crate::Peripheral<P = Self> {} | pub trait Instance: sealed::Instance + crate::Peripheral<P = Self> {} | ||||||
| #[cfg(any(adc_f1, adc_v1, adc_v2, adc_v3, adc_v4, adc_f3, adc_g0))] | #[cfg(any(adc_f1, adc_v1, adc_v2, adc_v3, adc_v4, adc_f3, adc_f3_v1_1, adc_g0))] | ||||||
| pub trait Instance: sealed::Instance + crate::Peripheral<P = Self> + crate::rcc::RccPeripheral {} | pub trait Instance: sealed::Instance + crate::Peripheral<P = Self> + crate::rcc::RccPeripheral {} | ||||||
|  |  | ||||||
| pub trait AdcPin<T: Instance>: sealed::AdcPin<T> {} | pub trait AdcPin<T: Instance>: sealed::AdcPin<T> {} | ||||||
| @@ -89,7 +90,7 @@ foreach_adc!( | |||||||
|                 crate::pac::$inst |                 crate::pac::$inst | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             #[cfg(not(any(adc_f1, adc_v1, adc_f3_v2, adc_g0)))] |             #[cfg(not(any(adc_f1, adc_v1, adc_f3_v2, adc_f3_v1_1, adc_g0)))] | ||||||
|             fn common_regs() -> crate::pac::adccommon::AdcCommon { |             fn common_regs() -> crate::pac::adccommon::AdcCommon { | ||||||
|                 return crate::pac::$common_inst |                 return crate::pac::$common_inst | ||||||
|             } |             } | ||||||
| @@ -99,7 +100,7 @@ foreach_adc!( | |||||||
|                 unsafe { crate::rcc::get_freqs() }.$clock.unwrap() |                 unsafe { crate::rcc::get_freqs() }.$clock.unwrap() | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             #[cfg(any(adc_f1, adc_f3, adc_v1))] |             #[cfg(any(adc_f1, adc_f3, adc_v1, adc_f3_v1_1))] | ||||||
|             fn state() -> &'static sealed::State { |             fn state() -> &'static sealed::State { | ||||||
|                 static STATE: sealed::State = sealed::State::new(); |                 static STATE: sealed::State = sealed::State::new(); | ||||||
|                 &STATE |                 &STATE | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| #[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3))] | #[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3, adc_f3_v1_1))] | ||||||
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] | #[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||||||
|  | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||||
| pub enum Resolution { | pub enum Resolution { | ||||||
|     TwelveBit, |     TwelveBit, | ||||||
|     TenBit, |     TenBit, | ||||||
| @@ -9,6 +10,7 @@ pub enum Resolution { | |||||||
|  |  | ||||||
| #[cfg(adc_v4)] | #[cfg(adc_v4)] | ||||||
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] | #[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||||||
|  | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||||
| pub enum Resolution { | pub enum Resolution { | ||||||
|     SixteenBit, |     SixteenBit, | ||||||
|     FourteenBit, |     FourteenBit, | ||||||
| @@ -19,7 +21,7 @@ pub enum Resolution { | |||||||
|  |  | ||||||
| impl Default for Resolution { | impl Default for Resolution { | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         #[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3))] |         #[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3, adc_f3_v1_1))] | ||||||
|         { |         { | ||||||
|             Self::TwelveBit |             Self::TwelveBit | ||||||
|         } |         } | ||||||
| @@ -40,7 +42,7 @@ impl From<Resolution> for crate::pac::adc::vals::Res { | |||||||
|             Resolution::TwelveBit => crate::pac::adc::vals::Res::TWELVEBIT, |             Resolution::TwelveBit => crate::pac::adc::vals::Res::TWELVEBIT, | ||||||
|             Resolution::TenBit => crate::pac::adc::vals::Res::TENBIT, |             Resolution::TenBit => crate::pac::adc::vals::Res::TENBIT, | ||||||
|             Resolution::EightBit => crate::pac::adc::vals::Res::EIGHTBIT, |             Resolution::EightBit => crate::pac::adc::vals::Res::EIGHTBIT, | ||||||
|             #[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3))] |             #[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3, adc_f3_v1_1))] | ||||||
|             Resolution::SixBit => crate::pac::adc::vals::Res::SIXBIT, |             Resolution::SixBit => crate::pac::adc::vals::Res::SIXBIT, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -56,7 +58,7 @@ impl Resolution { | |||||||
|             Resolution::TwelveBit => (1 << 12) - 1, |             Resolution::TwelveBit => (1 << 12) - 1, | ||||||
|             Resolution::TenBit => (1 << 10) - 1, |             Resolution::TenBit => (1 << 10) - 1, | ||||||
|             Resolution::EightBit => (1 << 8) - 1, |             Resolution::EightBit => (1 << 8) - 1, | ||||||
|             #[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3))] |             #[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3, adc_f3_v1_1))] | ||||||
|             Resolution::SixBit => (1 << 6) - 1, |             Resolution::SixBit => (1 << 6) - 1, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ macro_rules! impl_sample_time { | |||||||
|     ($default_doc:expr, $default:ident, ($(($doc:expr, $variant:ident, $pac_variant:ident)),*)) => { |     ($default_doc:expr, $default:ident, ($(($doc:expr, $variant:ident, $pac_variant:ident)),*)) => { | ||||||
|         #[doc = concat!("ADC sample time\n\nThe default setting is ", $default_doc, " ADC clock cycles.")] |         #[doc = concat!("ADC sample time\n\nThe default setting is ", $default_doc, " ADC clock cycles.")] | ||||||
|         #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] |         #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] | ||||||
|  |         #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||||
|         pub enum SampleTime { |         pub enum SampleTime { | ||||||
|             $( |             $( | ||||||
|                 #[doc = concat!($doc, " ADC clock cycles.")] |                 #[doc = concat!($doc, " ADC clock cycles.")] | ||||||
| @@ -18,6 +19,14 @@ macro_rules! impl_sample_time { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         impl From<crate::pac::adc::vals::SampleTime> for SampleTime { | ||||||
|  |             fn from(sample_time: crate::pac::adc::vals::SampleTime) -> SampleTime { | ||||||
|  |                 match sample_time { | ||||||
|  |                     $(crate::pac::adc::vals::SampleTime::$pac_variant => SampleTime::$variant),* | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         impl Default for SampleTime { |         impl Default for SampleTime { | ||||||
|             fn default() -> Self { |             fn default() -> Self { | ||||||
|                 Self::$default |                 Self::$default | ||||||
| @@ -121,3 +130,19 @@ impl_sample_time!( | |||||||
|         ("601.5", Cycles601_5, CYCLES601_5) |         ("601.5", Cycles601_5, CYCLES601_5) | ||||||
|     ) |     ) | ||||||
| ); | ); | ||||||
|  |  | ||||||
|  | #[cfg(any(adc_f3_v1_1))] | ||||||
|  | impl_sample_time!( | ||||||
|  |     "4", | ||||||
|  |     Cycles4, | ||||||
|  |     ( | ||||||
|  |         ("4", Cycles4, CYCLES4), | ||||||
|  |         ("9", Cycles9, CYCLES9), | ||||||
|  |         ("16", Cycles16, CYCLES16), | ||||||
|  |         ("24", Cycles24, CYCLES24), | ||||||
|  |         ("48", Cycles48, CYCLES48), | ||||||
|  |         ("96", Cycles96, CYCLES96), | ||||||
|  |         ("192", Cycles192, CYCLES192), | ||||||
|  |         ("384", Cycles384, CYCLES384) | ||||||
|  |     ) | ||||||
|  | ); | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| #![macro_use] | #![macro_use] | ||||||
|  |  | ||||||
| #[cfg_attr(can_bxcan, path = "bxcan.rs")] | #[cfg_attr(can_bxcan, path = "bxcan.rs")] | ||||||
| #[cfg_attr(can_fdcan, path = "fdcan.rs")] | #[cfg_attr(any(can_fdcan_v1, can_fdcan_h7), path = "fdcan.rs")] | ||||||
| mod _version; | mod _version; | ||||||
| pub use _version::*; | pub use _version::*; | ||||||
|   | |||||||
| @@ -90,6 +90,29 @@ pub use crate::_generated::interrupt; | |||||||
| /// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) | /// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) | ||||||
| /// and implements the right [`Binding`]s for it. You can pass this struct to drivers to | /// and implements the right [`Binding`]s for it. You can pass this struct to drivers to | ||||||
| /// prove at compile-time that the right interrupts have been bound. | /// prove at compile-time that the right interrupts have been bound. | ||||||
|  | /// | ||||||
|  | /// Example of how to bind one interrupt: | ||||||
|  | /// | ||||||
|  | /// ```rust,ignore | ||||||
|  | /// use embassy_stm32::{bind_interrupts, usb_otg, peripherals}; | ||||||
|  | /// | ||||||
|  | /// bind_interrupts!(struct Irqs { | ||||||
|  | ///     OTG_FS => usb_otg::InterruptHandler<peripherals::USB_OTG_FS>; | ||||||
|  | /// }); | ||||||
|  | /// ``` | ||||||
|  | /// | ||||||
|  | /// Example of how to bind multiple interrupts, and multiple handlers to each interrupt, in a single macro invocation: | ||||||
|  | /// | ||||||
|  | /// ```rust,ignore | ||||||
|  | /// use embassy_stm32::{bind_interrupts, i2c, peripherals}; | ||||||
|  | /// | ||||||
|  | /// bind_interrupts!(struct Irqs { | ||||||
|  | ///     I2C1 => i2c::EventInterruptHandler<peripherals::I2C1>, i2c::ErrorInterruptHandler<peripherals::I2C1>; | ||||||
|  | ///     I2C2_3 => i2c::EventInterruptHandler<peripherals::I2C2>, i2c::ErrorInterruptHandler<peripherals::I2C2>, | ||||||
|  | ///         i2c::EventInterruptHandler<peripherals::I2C3>, i2c::ErrorInterruptHandler<peripherals::I2C3>; | ||||||
|  | /// }); | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
| // developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. | // developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. | ||||||
| #[macro_export] | #[macro_export] | ||||||
| macro_rules! bind_interrupts { | macro_rules! bind_interrupts { | ||||||
|   | |||||||
| @@ -315,6 +315,8 @@ pub(crate) unsafe fn init(config: Config) { | |||||||
|         adc: adc12_ck, |         adc: adc12_ck, | ||||||
|         adc34: adc345_ck, |         adc34: adc345_ck, | ||||||
|         pll1_p: None, |         pll1_p: None, | ||||||
|  |         pll1_q: None, // TODO | ||||||
|  |         hse: None,    // TODO | ||||||
|         rtc, |         rtc, | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -119,7 +119,7 @@ pub struct Clocks { | |||||||
|  |  | ||||||
|     #[cfg(any(stm32g4, rcc_l4))] |     #[cfg(any(stm32g4, rcc_l4))] | ||||||
|     pub pll1_p: Option<Hertz>, |     pub pll1_p: Option<Hertz>, | ||||||
|     #[cfg(any(stm32h5, stm32h7, rcc_f2, rcc_f4, rcc_f410, rcc_f7, rcc_l4))] |     #[cfg(any(stm32h5, stm32h7, stm32f2, stm32f4, stm32f7, rcc_l4, stm32g4))] | ||||||
|     pub pll1_q: Option<Hertz>, |     pub pll1_q: Option<Hertz>, | ||||||
|     #[cfg(any(stm32h5, stm32h7))] |     #[cfg(any(stm32h5, stm32h7))] | ||||||
|     pub pll2_p: Option<Hertz>, |     pub pll2_p: Option<Hertz>, | ||||||
| @@ -167,7 +167,7 @@ pub struct Clocks { | |||||||
|  |  | ||||||
|     #[cfg(any(stm32h5, stm32h7, rcc_l4, rcc_c0))] |     #[cfg(any(stm32h5, stm32h7, rcc_l4, rcc_c0))] | ||||||
|     pub lse: Option<Hertz>, |     pub lse: Option<Hertz>, | ||||||
|     #[cfg(any(stm32h5, stm32h7))] |     #[cfg(any(stm32h5, stm32h7, stm32g4))] | ||||||
|     pub hse: Option<Hertz>, |     pub hse: Option<Hertz>, | ||||||
|  |  | ||||||
|     #[cfg(stm32h5)] |     #[cfg(stm32h5)] | ||||||
|   | |||||||
| @@ -10,14 +10,14 @@ stm32c031c6 = ["embassy-stm32/stm32c031c6", "cm0", "not-gpdma"] | |||||||
| stm32f103c8 = ["embassy-stm32/stm32f103c8", "not-gpdma"] | stm32f103c8 = ["embassy-stm32/stm32f103c8", "not-gpdma"] | ||||||
| stm32f207zg = ["embassy-stm32/stm32f207zg", "chrono", "not-gpdma", "eth", "rng"] | stm32f207zg = ["embassy-stm32/stm32f207zg", "chrono", "not-gpdma", "eth", "rng"] | ||||||
| stm32f303ze = ["embassy-stm32/stm32f303ze", "chrono", "not-gpdma"] | stm32f303ze = ["embassy-stm32/stm32f303ze", "chrono", "not-gpdma"] | ||||||
| stm32f429zi = ["embassy-stm32/stm32f429zi", "chrono", "eth", "stop", "can", "not-gpdma", "dac-adc-pin", "rng"] | stm32f429zi = ["embassy-stm32/stm32f429zi", "chrono", "eth", "stop", "can", "not-gpdma", "dac", "rng"] | ||||||
| stm32f446re = ["embassy-stm32/stm32f446re", "chrono", "stop", "can", "not-gpdma", "dac-adc-pin", "sdmmc"] | stm32f446re = ["embassy-stm32/stm32f446re", "chrono", "stop", "can", "not-gpdma", "dac", "sdmmc"] | ||||||
| stm32f767zi = ["embassy-stm32/stm32f767zi", "chrono", "not-gpdma", "eth", "rng"] | stm32f767zi = ["embassy-stm32/stm32f767zi", "chrono", "not-gpdma", "eth", "rng"] | ||||||
| stm32g071rb = ["embassy-stm32/stm32g071rb", "cm0", "not-gpdma", "dac-adc-pin"] | stm32g071rb = ["embassy-stm32/stm32g071rb", "cm0", "not-gpdma", "dac"] | ||||||
| stm32g491re = ["embassy-stm32/stm32g491re", "chrono", "stop", "not-gpdma", "rng"] | stm32g491re = ["embassy-stm32/stm32g491re", "chrono", "stop", "not-gpdma", "rng"] | ||||||
| stm32h563zi = ["embassy-stm32/stm32h563zi", "chrono", "eth", "rng"] | stm32h563zi = ["embassy-stm32/stm32h563zi", "chrono", "eth", "rng"] | ||||||
| stm32h753zi = ["embassy-stm32/stm32h753zi", "chrono", "not-gpdma", "eth", "rng"] | stm32h753zi = ["embassy-stm32/stm32h753zi", "chrono", "not-gpdma", "eth", "rng"] | ||||||
| stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "chrono", "not-gpdma", "eth", "dac-adc-pin", "rng"] | stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "chrono", "not-gpdma", "eth", "dac", "rng"] | ||||||
| stm32h7a3zi = ["embassy-stm32/stm32h7a3zi", "not-gpdma", "rng"] | stm32h7a3zi = ["embassy-stm32/stm32h7a3zi", "not-gpdma", "rng"] | ||||||
| stm32l073rz = ["embassy-stm32/stm32l073rz", "cm0", "not-gpdma", "rng"] | stm32l073rz = ["embassy-stm32/stm32l073rz", "cm0", "not-gpdma", "rng"] | ||||||
| stm32l152re = ["embassy-stm32/stm32l152re", "chrono", "not-gpdma"] | stm32l152re = ["embassy-stm32/stm32l152re", "chrono", "not-gpdma"] | ||||||
| @@ -41,7 +41,7 @@ ble = ["dep:embassy-stm32-wpan", "embassy-stm32-wpan/ble"] | |||||||
| mac = ["dep:embassy-stm32-wpan", "embassy-stm32-wpan/mac"] | mac = ["dep:embassy-stm32-wpan", "embassy-stm32-wpan/mac"] | ||||||
| embassy-stm32-wpan = [] | embassy-stm32-wpan = [] | ||||||
| not-gpdma = [] | not-gpdma = [] | ||||||
| dac-adc-pin = [] | dac = [] | ||||||
|  |  | ||||||
| cm0 = ["portable-atomic/unsafe-assume-single-core"] | cm0 = ["portable-atomic/unsafe-assume-single-core"] | ||||||
|  |  | ||||||
| @@ -84,7 +84,12 @@ required-features = [ "can",] | |||||||
| [[bin]] | [[bin]] | ||||||
| name = "dac" | name = "dac" | ||||||
| path = "src/bin/dac.rs" | path = "src/bin/dac.rs" | ||||||
| required-features = [ "dac-adc-pin",] | required-features = [ "dac",] | ||||||
|  |  | ||||||
|  | [[bin]] | ||||||
|  | name = "dac_l1" | ||||||
|  | path = "src/bin/dac_l1.rs" | ||||||
|  | required-features = [ "stm32l152re",] | ||||||
|  |  | ||||||
| [[bin]] | [[bin]] | ||||||
| name = "eth" | name = "eth" | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| #![no_std] | #![no_std] | ||||||
| #![no_main] | #![no_main] | ||||||
|  |  | ||||||
| // required-features: dac-adc-pin | // required-features: dac | ||||||
|  |  | ||||||
| #[path = "../common.rs"] | #[path = "../common.rs"] | ||||||
| mod common; | mod common; | ||||||
| @@ -22,12 +22,13 @@ async fn main(_spawner: Spawner) { | |||||||
|     // Initialize the board and obtain a Peripherals instance |     // Initialize the board and obtain a Peripherals instance | ||||||
|     let p: embassy_stm32::Peripherals = embassy_stm32::init(config()); |     let p: embassy_stm32::Peripherals = embassy_stm32::init(config()); | ||||||
|  |  | ||||||
|  |     let adc = peri!(p, ADC); | ||||||
|     let dac = peri!(p, DAC); |     let dac = peri!(p, DAC); | ||||||
|     let dac_pin = peri!(p, DAC_PIN); |     let dac_pin = peri!(p, DAC_PIN); | ||||||
|     let mut adc_pin = unsafe { core::ptr::read(&dac_pin) }; |     let mut adc_pin = unsafe { core::ptr::read(&dac_pin) }; | ||||||
|  |  | ||||||
|     let mut dac = DacCh1::new(dac, NoDma, dac_pin); |     let mut dac = DacCh1::new(dac, NoDma, dac_pin); | ||||||
|     let mut adc = Adc::new(p.ADC1, &mut Delay); |     let mut adc = Adc::new(adc, &mut Delay); | ||||||
|  |  | ||||||
|     #[cfg(feature = "stm32h755zi")] |     #[cfg(feature = "stm32h755zi")] | ||||||
|     let normalization_factor = 256; |     let normalization_factor = 256; | ||||||
|   | |||||||
							
								
								
									
										86
									
								
								tests/stm32/src/bin/dac_l1.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								tests/stm32/src/bin/dac_l1.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | #![no_std] | ||||||
|  | #![no_main] | ||||||
|  |  | ||||||
|  | // required-features: stm32l152re | ||||||
|  |  | ||||||
|  | #[path = "../common.rs"] | ||||||
|  | mod common; | ||||||
|  | use core::f32::consts::PI; | ||||||
|  |  | ||||||
|  | use common::*; | ||||||
|  | use defmt::assert; | ||||||
|  | use embassy_executor::Spawner; | ||||||
|  | use embassy_stm32::adc::Adc; | ||||||
|  | use embassy_stm32::dac::{DacCh1, Value}; | ||||||
|  | use embassy_stm32::dma::NoDma; | ||||||
|  | use embassy_stm32::{bind_interrupts, peripherals}; | ||||||
|  | use embassy_time::Timer; | ||||||
|  | use micromath::F32Ext; | ||||||
|  | use {defmt_rtt as _, panic_probe as _}; | ||||||
|  |  | ||||||
|  | bind_interrupts!(struct Irqs { | ||||||
|  |     ADC1 => embassy_stm32::adc::InterruptHandler<peripherals::ADC>; | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | #[embassy_executor::main] | ||||||
|  | async fn main(_spawner: Spawner) { | ||||||
|  |     // Initialize the board and obtain a Peripherals instance | ||||||
|  |     let p: embassy_stm32::Peripherals = embassy_stm32::init(config()); | ||||||
|  |  | ||||||
|  |     let adc = peri!(p, ADC); | ||||||
|  |     let dac = peri!(p, DAC); | ||||||
|  |     let dac_pin = peri!(p, DAC_PIN); | ||||||
|  |     let mut adc_pin = unsafe { core::ptr::read(&dac_pin) }; | ||||||
|  |  | ||||||
|  |     let mut dac = DacCh1::new(dac, NoDma, dac_pin); | ||||||
|  |     let mut adc = Adc::new(adc, Irqs); | ||||||
|  |  | ||||||
|  |     #[cfg(feature = "stm32h755zi")] | ||||||
|  |     let normalization_factor = 256; | ||||||
|  |     #[cfg(any( | ||||||
|  |         feature = "stm32f429zi", | ||||||
|  |         feature = "stm32f446re", | ||||||
|  |         feature = "stm32g071rb", | ||||||
|  |         feature = "stm32l152re", | ||||||
|  |     ))] | ||||||
|  |     let normalization_factor: i32 = 16; | ||||||
|  |  | ||||||
|  |     dac.set(Value::Bit8(0)); | ||||||
|  |     // Now wait a little to obtain a stable value | ||||||
|  |     Timer::after_millis(30).await; | ||||||
|  |     let offset = adc.read(&mut adc_pin).await; | ||||||
|  |  | ||||||
|  |     for v in 0..=255 { | ||||||
|  |         // First set the DAC output value | ||||||
|  |         let dac_output_val = to_sine_wave(v); | ||||||
|  |         dac.set(Value::Bit8(dac_output_val)); | ||||||
|  |  | ||||||
|  |         // Now wait a little to obtain a stable value | ||||||
|  |         Timer::after_millis(30).await; | ||||||
|  |  | ||||||
|  |         // Need to steal the peripherals here because PA4 is obviously in use already | ||||||
|  |         let measured = adc.read(&mut unsafe { embassy_stm32::Peripherals::steal() }.PA4).await; | ||||||
|  |         // Calibrate and normalize the measurement to get close to the dac_output_val | ||||||
|  |         let measured_normalized = ((measured as i32 - offset as i32) / normalization_factor) as i16; | ||||||
|  |  | ||||||
|  |         info!("value / measured: {} / {}", dac_output_val, measured_normalized); | ||||||
|  |  | ||||||
|  |         // The deviations are quite enormous but that does not matter since this is only a quick test | ||||||
|  |         assert!((dac_output_val as i16 - measured_normalized).abs() < 15); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     info!("Test OK"); | ||||||
|  |     cortex_m::asm::bkpt(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn to_sine_wave(v: u8) -> u8 { | ||||||
|  |     if v >= 128 { | ||||||
|  |         // top half | ||||||
|  |         let r = PI * ((v - 128) as f32 / 128.0); | ||||||
|  |         (r.sin() * 128.0 + 127.0) as u8 | ||||||
|  |     } else { | ||||||
|  |         // bottom half | ||||||
|  |         let r = PI + PI * (v as f32 / 128.0); | ||||||
|  |         (r.sin() * 128.0 + 127.0) as u8 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -101,14 +101,14 @@ define_peris!( | |||||||
| define_peris!( | define_peris!( | ||||||
|     UART = USART1, UART_TX = PC4, UART_RX = PC5, UART_TX_DMA = DMA1_CH1, UART_RX_DMA = DMA1_CH2, |     UART = USART1, UART_TX = PC4, UART_RX = PC5, UART_TX_DMA = DMA1_CH1, UART_RX_DMA = DMA1_CH2, | ||||||
|     SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH1, SPI_RX_DMA = DMA1_CH2, |     SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH1, SPI_RX_DMA = DMA1_CH2, | ||||||
|     DAC = DAC1, DAC_PIN = PA4, |     ADC = ADC1, DAC = DAC1, DAC_PIN = PA4, | ||||||
|     @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART1>;}, |     @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART1>;}, | ||||||
| ); | ); | ||||||
| #[cfg(feature = "stm32f429zi")] | #[cfg(feature = "stm32f429zi")] | ||||||
| define_peris!( | define_peris!( | ||||||
|     UART = USART6, UART_TX = PG14, UART_RX = PG9, UART_TX_DMA = DMA2_CH6, UART_RX_DMA = DMA2_CH1, |     UART = USART6, UART_TX = PG14, UART_RX = PG9, UART_TX_DMA = DMA2_CH6, UART_RX_DMA = DMA2_CH1, | ||||||
|     SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA2_CH3, SPI_RX_DMA = DMA2_CH2, |     SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA2_CH3, SPI_RX_DMA = DMA2_CH2, | ||||||
|     DAC = DAC, DAC_PIN = PA4, |     ADC = ADC1, DAC = DAC, DAC_PIN = PA4, | ||||||
|     CAN = CAN1, CAN_RX = PD0, CAN_TX = PD1, |     CAN = CAN1, CAN_RX = PD0, CAN_TX = PD1, | ||||||
|     @irq UART = {USART6 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART6>;}, |     @irq UART = {USART6 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART6>;}, | ||||||
| ); | ); | ||||||
| @@ -116,7 +116,7 @@ define_peris!( | |||||||
| define_peris!( | define_peris!( | ||||||
|     UART = USART1, UART_TX = PA9, UART_RX = PA10, UART_TX_DMA = DMA2_CH7, UART_RX_DMA = DMA2_CH5, |     UART = USART1, UART_TX = PA9, UART_RX = PA10, UART_TX_DMA = DMA2_CH7, UART_RX_DMA = DMA2_CH5, | ||||||
|     SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA2_CH3, SPI_RX_DMA = DMA2_CH2, |     SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA2_CH3, SPI_RX_DMA = DMA2_CH2, | ||||||
|     DAC = DAC, DAC_PIN = PA4, |     ADC = ADC1, DAC = DAC, DAC_PIN = PA4, | ||||||
|     CAN = CAN1, CAN_RX = PA11, CAN_TX = PA12, |     CAN = CAN1, CAN_RX = PA11, CAN_TX = PA12, | ||||||
|     @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART1>;}, |     @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART1>;}, | ||||||
| ); | ); | ||||||
| @@ -130,7 +130,7 @@ define_peris!( | |||||||
| define_peris!( | define_peris!( | ||||||
|     UART = USART1, UART_TX = PB6, UART_RX = PB7, UART_TX_DMA = DMA1_CH0, UART_RX_DMA = DMA1_CH1, |     UART = USART1, UART_TX = PB6, UART_RX = PB7, UART_TX_DMA = DMA1_CH0, UART_RX_DMA = DMA1_CH1, | ||||||
|     SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PB5, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH0, SPI_RX_DMA = DMA1_CH1, |     SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PB5, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH0, SPI_RX_DMA = DMA1_CH1, | ||||||
|     DAC = DAC1, DAC_PIN = PA4, |     ADC = ADC1, DAC = DAC1, DAC_PIN = PA4, | ||||||
|     @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART1>;}, |     @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART1>;}, | ||||||
| ); | ); | ||||||
| #[cfg(feature = "stm32h7a3zi")] | #[cfg(feature = "stm32h7a3zi")] | ||||||
| @@ -191,6 +191,7 @@ define_peris!( | |||||||
| define_peris!( | define_peris!( | ||||||
|     UART = USART3, UART_TX = PB10, UART_RX = PB11, UART_TX_DMA = DMA1_CH2, UART_RX_DMA = DMA1_CH3, |     UART = USART3, UART_TX = PB10, UART_RX = PB11, UART_TX_DMA = DMA1_CH2, UART_RX_DMA = DMA1_CH3, | ||||||
|     SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH3, SPI_RX_DMA = DMA1_CH2, |     SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH3, SPI_RX_DMA = DMA1_CH2, | ||||||
|  |     ADC = ADC, DAC = DAC, DAC_PIN = PA4, | ||||||
|     @irq UART = {USART3 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART3>;}, |     @irq UART = {USART3 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART3>;}, | ||||||
| ); | ); | ||||||
| #[cfg(feature = "stm32l552ze")] | #[cfg(feature = "stm32l552ze")] | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user