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:project_structure.adoc[Project Structure] | ||||
| ** 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:runtime.adoc[Executor] | ||||
| * xref:delaying_a_task.adoc[Delaying a Task] | ||||
| @@ -11,7 +12,7 @@ | ||||
| * xref:bootloader.adoc[Bootloader] | ||||
|  | ||||
| * xref:examples.adoc[Examples] | ||||
| * xref:developer.adoc[Developer] | ||||
| ** xref:developer_stm32.adoc[Developer: STM32] | ||||
| * xref:developer.adoc[Developer Docs] | ||||
| ** xref:developer_stm32.adoc[Developer Docs: STM32] | ||||
| * xref:embassy_in_the_wild.adoc[Embassy in the wild] | ||||
| * 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 | ||||
| lto = true | ||||
| opt-level = "s" | ||||
| incremental = true | ||||
| incremental = false | ||||
| codegen-units = 1 | ||||
| ---- | ||||
|  | ||||
| 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! | ||||
|  | ||||
| == 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;`) | ||||
| /// 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. | ||||
| /// | ||||
| /// 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`. | ||||
| #[macro_export] | ||||
| 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;`) | ||||
| /// 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. | ||||
| /// | ||||
| /// 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`. | ||||
| #[macro_export] | ||||
| macro_rules! bind_interrupts { | ||||
|   | ||||
| @@ -58,7 +58,7 @@ rand_core = "0.6.3" | ||||
| sdio-host = "0.5.0" | ||||
| embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true } | ||||
| 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" | ||||
| bxcan = "0.7.0" | ||||
| nb = "1.0.0" | ||||
| @@ -76,7 +76,7 @@ critical-section = { version = "1.1", features = ["std"] } | ||||
| [build-dependencies] | ||||
| proc-macro2 = "1.0.36" | ||||
| 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] | ||||
|   | ||||
| @@ -930,6 +930,10 @@ fn main() { | ||||
|                     } else if pin.signal.starts_with("INN") { | ||||
|                         // TODO handle in the future when embassy supports differential measurements | ||||
|                         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") { | ||||
|                         Some(pin.signal.strip_prefix("IN").unwrap().parse().unwrap()) | ||||
|                     } else { | ||||
|   | ||||
| @@ -148,7 +148,7 @@ impl<'d, T: Instance> Adc<'d, T> { | ||||
|             reg.set_cont(false); | ||||
|             reg.set_exttrig(true); | ||||
|             reg.set_swstart(false); | ||||
|             reg.set_extsel(crate::pac::adc::vals::Extsel::SWSTART); | ||||
|             reg.set_extsel(7); // SWSTART | ||||
|         }); | ||||
|  | ||||
|         // 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_attr(adc_f1, path = "f1.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_v2, path = "v2.rs")] | ||||
| #[cfg_attr(any(adc_v3, adc_g0), path = "v3.rs")] | ||||
| @@ -26,20 +27,20 @@ use crate::peripherals; | ||||
| pub struct Adc<'d, T: Instance> { | ||||
|     #[allow(unused)] | ||||
|     adc: crate::PeripheralRef<'d, T>, | ||||
|     #[cfg(not(adc_f3_v2))] | ||||
|     #[cfg(not(any(adc_f3_v2, adc_f3_v1_1)))] | ||||
|     sample_time: SampleTime, | ||||
| } | ||||
|  | ||||
| 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; | ||||
|  | ||||
|     #[cfg(any(adc_f1, adc_f3, adc_v1))] | ||||
|     #[cfg(any(adc_f1, adc_f3, adc_v1, adc_f3_v1_1))] | ||||
|     pub struct State { | ||||
|         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 { | ||||
|         pub const fn new() -> Self { | ||||
|             Self { | ||||
| @@ -54,11 +55,11 @@ pub(crate) mod sealed { | ||||
|  | ||||
|     pub trait Instance: InterruptableInstance { | ||||
|         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; | ||||
|         #[cfg(adc_f3)] | ||||
|         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; | ||||
|     } | ||||
|  | ||||
| @@ -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> {} | ||||
| #[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 AdcPin<T: Instance>: sealed::AdcPin<T> {} | ||||
| @@ -89,7 +90,7 @@ foreach_adc!( | ||||
|                 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 { | ||||
|                 return crate::pac::$common_inst | ||||
|             } | ||||
| @@ -99,7 +100,7 @@ foreach_adc!( | ||||
|                 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 { | ||||
|                 static STATE: sealed::State = sealed::State::new(); | ||||
|                 &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)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| pub enum Resolution { | ||||
|     TwelveBit, | ||||
|     TenBit, | ||||
| @@ -9,6 +10,7 @@ pub enum Resolution { | ||||
|  | ||||
| #[cfg(adc_v4)] | ||||
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| pub enum Resolution { | ||||
|     SixteenBit, | ||||
|     FourteenBit, | ||||
| @@ -19,7 +21,7 @@ pub enum Resolution { | ||||
|  | ||||
| impl Default for Resolution { | ||||
|     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 | ||||
|         } | ||||
| @@ -40,7 +42,7 @@ impl From<Resolution> for crate::pac::adc::vals::Res { | ||||
|             Resolution::TwelveBit => crate::pac::adc::vals::Res::TWELVEBIT, | ||||
|             Resolution::TenBit => crate::pac::adc::vals::Res::TENBIT, | ||||
|             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, | ||||
|         } | ||||
|     } | ||||
| @@ -56,7 +58,7 @@ impl Resolution { | ||||
|             Resolution::TwelveBit => (1 << 12) - 1, | ||||
|             Resolution::TenBit => (1 << 10) - 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, | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -3,6 +3,7 @@ macro_rules! impl_sample_time { | ||||
|     ($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.")] | ||||
|         #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] | ||||
|         #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
|         pub enum SampleTime { | ||||
|             $( | ||||
|                 #[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 { | ||||
|             fn default() -> Self { | ||||
|                 Self::$default | ||||
| @@ -121,3 +130,19 @@ impl_sample_time!( | ||||
|         ("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] | ||||
|  | ||||
| #[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; | ||||
| 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;`) | ||||
| /// 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. | ||||
| /// | ||||
| /// 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`. | ||||
| #[macro_export] | ||||
| macro_rules! bind_interrupts { | ||||
|   | ||||
| @@ -315,6 +315,8 @@ pub(crate) unsafe fn init(config: Config) { | ||||
|         adc: adc12_ck, | ||||
|         adc34: adc345_ck, | ||||
|         pll1_p: None, | ||||
|         pll1_q: None, // TODO | ||||
|         hse: None,    // TODO | ||||
|         rtc, | ||||
|     }); | ||||
| } | ||||
|   | ||||
| @@ -119,7 +119,7 @@ pub struct Clocks { | ||||
|  | ||||
|     #[cfg(any(stm32g4, rcc_l4))] | ||||
|     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>, | ||||
|     #[cfg(any(stm32h5, stm32h7))] | ||||
|     pub pll2_p: Option<Hertz>, | ||||
| @@ -167,7 +167,7 @@ pub struct Clocks { | ||||
|  | ||||
|     #[cfg(any(stm32h5, stm32h7, rcc_l4, rcc_c0))] | ||||
|     pub lse: Option<Hertz>, | ||||
|     #[cfg(any(stm32h5, stm32h7))] | ||||
|     #[cfg(any(stm32h5, stm32h7, stm32g4))] | ||||
|     pub hse: Option<Hertz>, | ||||
|  | ||||
|     #[cfg(stm32h5)] | ||||
|   | ||||
| @@ -10,14 +10,14 @@ stm32c031c6 = ["embassy-stm32/stm32c031c6", "cm0", "not-gpdma"] | ||||
| stm32f103c8 = ["embassy-stm32/stm32f103c8", "not-gpdma"] | ||||
| stm32f207zg = ["embassy-stm32/stm32f207zg", "chrono", "not-gpdma", "eth", "rng"] | ||||
| stm32f303ze = ["embassy-stm32/stm32f303ze", "chrono", "not-gpdma"] | ||||
| stm32f429zi = ["embassy-stm32/stm32f429zi", "chrono", "eth", "stop", "can", "not-gpdma", "dac-adc-pin", "rng"] | ||||
| stm32f446re = ["embassy-stm32/stm32f446re", "chrono", "stop", "can", "not-gpdma", "dac-adc-pin", "sdmmc"] | ||||
| stm32f429zi = ["embassy-stm32/stm32f429zi", "chrono", "eth", "stop", "can", "not-gpdma", "dac", "rng"] | ||||
| stm32f446re = ["embassy-stm32/stm32f446re", "chrono", "stop", "can", "not-gpdma", "dac", "sdmmc"] | ||||
| 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"] | ||||
| stm32h563zi = ["embassy-stm32/stm32h563zi", "chrono", "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"] | ||||
| stm32l073rz = ["embassy-stm32/stm32l073rz", "cm0", "not-gpdma", "rng"] | ||||
| 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"] | ||||
| embassy-stm32-wpan = [] | ||||
| not-gpdma = [] | ||||
| dac-adc-pin = [] | ||||
| dac = [] | ||||
|  | ||||
| cm0 = ["portable-atomic/unsafe-assume-single-core"] | ||||
|  | ||||
| @@ -84,7 +84,12 @@ required-features = [ "can",] | ||||
| [[bin]] | ||||
| name = "dac" | ||||
| 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]] | ||||
| name = "eth" | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| #![no_std] | ||||
| #![no_main] | ||||
|  | ||||
| // required-features: dac-adc-pin | ||||
| // required-features: dac | ||||
|  | ||||
| #[path = "../common.rs"] | ||||
| mod common; | ||||
| @@ -22,12 +22,13 @@ 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(p.ADC1, &mut Delay); | ||||
|     let mut adc = Adc::new(adc, &mut Delay); | ||||
|  | ||||
|     #[cfg(feature = "stm32h755zi")] | ||||
|     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!( | ||||
|     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, | ||||
|     DAC = DAC1, DAC_PIN = PA4, | ||||
|     ADC = ADC1, DAC = DAC1, DAC_PIN = PA4, | ||||
|     @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART1>;}, | ||||
| ); | ||||
| #[cfg(feature = "stm32f429zi")] | ||||
| define_peris!( | ||||
|     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, | ||||
|     DAC = DAC, DAC_PIN = PA4, | ||||
|     ADC = ADC1, DAC = DAC, DAC_PIN = PA4, | ||||
|     CAN = CAN1, CAN_RX = PD0, CAN_TX = PD1, | ||||
|     @irq UART = {USART6 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART6>;}, | ||||
| ); | ||||
| @@ -116,7 +116,7 @@ define_peris!( | ||||
| define_peris!( | ||||
|     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, | ||||
|     DAC = DAC, DAC_PIN = PA4, | ||||
|     ADC = ADC1, DAC = DAC, DAC_PIN = PA4, | ||||
|     CAN = CAN1, CAN_RX = PA11, CAN_TX = PA12, | ||||
|     @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART1>;}, | ||||
| ); | ||||
| @@ -130,7 +130,7 @@ define_peris!( | ||||
| define_peris!( | ||||
|     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, | ||||
|     DAC = DAC1, DAC_PIN = PA4, | ||||
|     ADC = ADC1, DAC = DAC1, DAC_PIN = PA4, | ||||
|     @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART1>;}, | ||||
| ); | ||||
| #[cfg(feature = "stm32h7a3zi")] | ||||
| @@ -191,6 +191,7 @@ define_peris!( | ||||
| define_peris!( | ||||
|     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, | ||||
|     ADC = ADC, DAC = DAC, DAC_PIN = PA4, | ||||
|     @irq UART = {USART3 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART3>;}, | ||||
| ); | ||||
| #[cfg(feature = "stm32l552ze")] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user