diff options
| author | i509VCB <[email protected]> | 2025-09-17 05:18:03 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-09-17 05:18:03 +0000 |
| commit | bbc93851fb9fb31342cc44ba096bed84134427f6 (patch) | |
| tree | 08dc127a384184ffc77aea612dbf11a2617ba54c | |
| parent | ab81b797c25996d8649971c1570c4bb949763c5b (diff) | |
| parent | 31b5a3f0a4fafd425aef34b9d6fb93ead851b4c6 (diff) | |
Merge pull request #4646 from Iooon/mspm0-adc
MSPM0: add adc implementation
| -rw-r--r-- | embassy-mspm0/CHANGELOG.md | 1 | ||||
| -rw-r--r-- | embassy-mspm0/build.rs | 22 | ||||
| -rw-r--r-- | embassy-mspm0/src/adc.rs | 510 | ||||
| -rw-r--r-- | embassy-mspm0/src/lib.rs | 1 | ||||
| -rw-r--r-- | examples/mspm0g3507/src/bin/adc.rs | 39 | ||||
| -rw-r--r-- | examples/mspm0l1306/src/bin/adc.rs | 39 |
6 files changed, 612 insertions, 0 deletions
diff --git a/embassy-mspm0/CHANGELOG.md b/embassy-mspm0/CHANGELOG.md index c7da4eb33..b846f21b1 100644 --- a/embassy-mspm0/CHANGELOG.md +++ b/embassy-mspm0/CHANGELOG.md | |||
| @@ -12,3 +12,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 12 | - fix gpio interrupt not being set for mspm0l110x | 12 | - fix gpio interrupt not being set for mspm0l110x |
| 13 | - feat: Add window watchdog implementation based on WWDT0, WWDT1 peripherals (#4574) | 13 | - feat: Add window watchdog implementation based on WWDT0, WWDT1 peripherals (#4574) |
| 14 | - feat: Add MSPM0C1105/C1106 support | 14 | - feat: Add MSPM0C1105/C1106 support |
| 15 | - feat: Add adc implementation (#4646) | ||
diff --git a/embassy-mspm0/build.rs b/embassy-mspm0/build.rs index e8364e31a..d294bc422 100644 --- a/embassy-mspm0/build.rs +++ b/embassy-mspm0/build.rs | |||
| @@ -68,6 +68,7 @@ fn generate_code() { | |||
| 68 | g.extend(generate_pin_trait_impls()); | 68 | g.extend(generate_pin_trait_impls()); |
| 69 | g.extend(generate_groups()); | 69 | g.extend(generate_groups()); |
| 70 | g.extend(generate_dma_channel_count()); | 70 | g.extend(generate_dma_channel_count()); |
| 71 | g.extend(generate_adc_constants(&mut cfgs)); | ||
| 71 | 72 | ||
| 72 | let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); | 73 | let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); |
| 73 | let out_file = out_dir.join("_generated.rs").to_string_lossy().to_string(); | 74 | let out_file = out_dir.join("_generated.rs").to_string_lossy().to_string(); |
| @@ -220,6 +221,22 @@ fn generate_dma_channel_count() -> TokenStream { | |||
| 220 | quote! { pub const DMA_CHANNELS: usize = #count; } | 221 | quote! { pub const DMA_CHANNELS: usize = #count; } |
| 221 | } | 222 | } |
| 222 | 223 | ||
| 224 | fn generate_adc_constants(cfgs: &mut CfgSet) -> TokenStream { | ||
| 225 | let vrsel = METADATA.adc_vrsel; | ||
| 226 | let memctl = METADATA.adc_memctl; | ||
| 227 | |||
| 228 | cfgs.declare("adc_neg_vref"); | ||
| 229 | match vrsel { | ||
| 230 | 3 => (), | ||
| 231 | 5 => cfgs.enable("adc_neg_vref"), | ||
| 232 | _ => panic!("Unsupported ADC VRSEL value: {vrsel}"), | ||
| 233 | } | ||
| 234 | quote! { | ||
| 235 | pub const ADC_VRSEL: u8 = #vrsel; | ||
| 236 | pub const ADC_MEMCTL: u8 = #memctl; | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 223 | #[derive(Debug, Clone)] | 240 | #[derive(Debug, Clone)] |
| 224 | struct Singleton { | 241 | struct Singleton { |
| 225 | name: String, | 242 | name: String, |
| @@ -561,6 +578,7 @@ fn generate_peripheral_instances() -> TokenStream { | |||
| 561 | "uart" => Some(quote! { impl_uart_instance!(#peri); }), | 578 | "uart" => Some(quote! { impl_uart_instance!(#peri); }), |
| 562 | "i2c" => Some(quote! { impl_i2c_instance!(#peri, #fifo_size); }), | 579 | "i2c" => Some(quote! { impl_i2c_instance!(#peri, #fifo_size); }), |
| 563 | "wwdt" => Some(quote! { impl_wwdt_instance!(#peri); }), | 580 | "wwdt" => Some(quote! { impl_wwdt_instance!(#peri); }), |
| 581 | "adc" => Some(quote! { impl_adc_instance!(#peri); }), | ||
| 564 | _ => None, | 582 | _ => None, |
| 565 | }; | 583 | }; |
| 566 | 584 | ||
| @@ -609,6 +627,10 @@ fn generate_pin_trait_impls() -> TokenStream { | |||
| 609 | ("uart", "RTS") => Some(quote! { impl_uart_rts_pin!(#peri, #pin_name, #pf); }), | 627 | ("uart", "RTS") => Some(quote! { impl_uart_rts_pin!(#peri, #pin_name, #pf); }), |
| 610 | ("i2c", "SDA") => Some(quote! { impl_i2c_sda_pin!(#peri, #pin_name, #pf); }), | 628 | ("i2c", "SDA") => Some(quote! { impl_i2c_sda_pin!(#peri, #pin_name, #pf); }), |
| 611 | ("i2c", "SCL") => Some(quote! { impl_i2c_scl_pin!(#peri, #pin_name, #pf); }), | 629 | ("i2c", "SCL") => Some(quote! { impl_i2c_scl_pin!(#peri, #pin_name, #pf); }), |
| 630 | ("adc", s) => { | ||
| 631 | let signal = s.parse::<u8>().unwrap(); | ||
| 632 | Some(quote! { impl_adc_pin!(#peri, #pin_name, #signal); }) | ||
| 633 | } | ||
| 612 | 634 | ||
| 613 | _ => None, | 635 | _ => None, |
| 614 | }; | 636 | }; |
diff --git a/embassy-mspm0/src/adc.rs b/embassy-mspm0/src/adc.rs new file mode 100644 index 000000000..5b93e9a6e --- /dev/null +++ b/embassy-mspm0/src/adc.rs | |||
| @@ -0,0 +1,510 @@ | |||
| 1 | #![macro_use] | ||
| 2 | |||
| 3 | use core::future::poll_fn; | ||
| 4 | use core::marker::PhantomData; | ||
| 5 | use core::task::Poll; | ||
| 6 | |||
| 7 | use embassy_hal_internal::{impl_peripheral, PeripheralType}; | ||
| 8 | use embassy_sync::waitqueue::AtomicWaker; | ||
| 9 | |||
| 10 | use crate::interrupt::{Interrupt, InterruptExt}; | ||
| 11 | use crate::mode::{Async, Blocking, Mode}; | ||
| 12 | use crate::pac::adc::{vals, Adc as Regs}; | ||
| 13 | use crate::{interrupt, Peri}; | ||
| 14 | |||
| 15 | /// Interrupt handler. | ||
| 16 | pub struct InterruptHandler<T: Instance> { | ||
| 17 | _phantom: PhantomData<T>, | ||
| 18 | } | ||
| 19 | |||
| 20 | impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> { | ||
| 21 | unsafe fn on_interrupt() { | ||
| 22 | // Mis is cleared upon reading iidx | ||
| 23 | let iidx = T::info().regs.cpu_int(0).iidx().read().stat(); | ||
| 24 | // TODO: Running in sequence mode, we get an interrupt per finished result. It would be | ||
| 25 | // nice to wake up only after all results are finished. | ||
| 26 | if vals::CpuIntIidxStat::MEMRESIFG0 <= iidx && iidx <= vals::CpuIntIidxStat::MEMRESIFG23 { | ||
| 27 | T::state().waker.wake(); | ||
| 28 | } | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | // Constants from the metapac crate | ||
| 33 | const ADC_VRSEL: u8 = crate::_generated::ADC_VRSEL; | ||
| 34 | const ADC_MEMCTL: u8 = crate::_generated::ADC_MEMCTL; | ||
| 35 | |||
| 36 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] | ||
| 37 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 38 | /// Conversion resolution of the ADC results. | ||
| 39 | pub enum Resolution { | ||
| 40 | /// 12-bits resolution | ||
| 41 | BIT12, | ||
| 42 | |||
| 43 | /// 10-bits resolution | ||
| 44 | BIT10, | ||
| 45 | |||
| 46 | /// 8-bits resolution | ||
| 47 | BIT8, | ||
| 48 | } | ||
| 49 | |||
| 50 | impl Resolution { | ||
| 51 | /// Number of bits of the resolution. | ||
| 52 | pub fn bits(&self) -> u8 { | ||
| 53 | match self { | ||
| 54 | Resolution::BIT12 => 12, | ||
| 55 | Resolution::BIT10 => 10, | ||
| 56 | Resolution::BIT8 => 8, | ||
| 57 | } | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] | ||
| 62 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 63 | /// Reference voltage (Vref) selection for the ADC channels. | ||
| 64 | pub enum Vrsel { | ||
| 65 | /// VDDA reference | ||
| 66 | VddaVssa = 0, | ||
| 67 | |||
| 68 | /// External reference from pin | ||
| 69 | ExtrefVrefm = 1, | ||
| 70 | |||
| 71 | /// Internal reference | ||
| 72 | IntrefVssa = 2, | ||
| 73 | |||
| 74 | /// VDDA and VREFM connected to VREF+ and VREF- of ADC | ||
| 75 | #[cfg(adc_neg_vref)] | ||
| 76 | VddaVrefm = 3, | ||
| 77 | |||
| 78 | /// INTREF and VREFM connected to VREF+ and VREF- of ADC | ||
| 79 | #[cfg(adc_neg_vref)] | ||
| 80 | IntrefVrefm = 4, | ||
| 81 | } | ||
| 82 | |||
| 83 | /// ADC configuration. | ||
| 84 | #[derive(Copy, Clone)] | ||
| 85 | #[non_exhaustive] | ||
| 86 | pub struct Config { | ||
| 87 | /// Resolution of the ADC conversion. The number of bits used to represent an ADC measurement. | ||
| 88 | pub resolution: Resolution, | ||
| 89 | /// ADC voltage reference selection. | ||
| 90 | /// | ||
| 91 | /// This value is used when reading a single channel. When reading a sequence | ||
| 92 | /// the vr_select is provided per channel. | ||
| 93 | pub vr_select: Vrsel, | ||
| 94 | /// The sample time in number of ADC sample clock cycles. | ||
| 95 | pub sample_time: u16, | ||
| 96 | } | ||
| 97 | |||
| 98 | impl Default for Config { | ||
| 99 | fn default() -> Self { | ||
| 100 | Self { | ||
| 101 | resolution: Resolution::BIT12, | ||
| 102 | vr_select: Vrsel::VddaVssa, | ||
| 103 | sample_time: 50, | ||
| 104 | } | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | /// ADC (Analog to Digial Converter) Driver. | ||
| 109 | pub struct Adc<'d, T: Instance, M: Mode> { | ||
| 110 | #[allow(unused)] | ||
| 111 | adc: crate::Peri<'d, T>, | ||
| 112 | info: &'static Info, | ||
| 113 | state: &'static State, | ||
| 114 | config: Config, | ||
| 115 | _phantom: PhantomData<M>, | ||
| 116 | } | ||
| 117 | |||
| 118 | impl<'d, T: Instance> Adc<'d, T, Blocking> { | ||
| 119 | /// A new blocking ADC driver instance. | ||
| 120 | pub fn new_blocking(peri: Peri<'d, T>, config: Config) -> Self { | ||
| 121 | let mut this = Self { | ||
| 122 | adc: peri, | ||
| 123 | info: T::info(), | ||
| 124 | state: T::state(), | ||
| 125 | config, | ||
| 126 | _phantom: PhantomData, | ||
| 127 | }; | ||
| 128 | this.setup(); | ||
| 129 | this | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | impl<'d, T: Instance> Adc<'d, T, Async> { | ||
| 134 | /// A new asynchronous ADC driver instance. | ||
| 135 | pub fn new_async( | ||
| 136 | peri: Peri<'d, T>, | ||
| 137 | config: Config, | ||
| 138 | _irqs: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd, | ||
| 139 | ) -> Self { | ||
| 140 | let mut this = Self { | ||
| 141 | adc: peri, | ||
| 142 | info: T::info(), | ||
| 143 | state: T::state(), | ||
| 144 | config, | ||
| 145 | _phantom: PhantomData, | ||
| 146 | }; | ||
| 147 | this.setup(); | ||
| 148 | unsafe { | ||
| 149 | this.info.interrupt.enable(); | ||
| 150 | } | ||
| 151 | this | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | impl<'d, T: Instance, M: Mode> Adc<'d, T, M> { | ||
| 156 | const SINGLE_CHANNEL: u8 = 0; | ||
| 157 | |||
| 158 | fn setup(&mut self) { | ||
| 159 | let config = &self.config; | ||
| 160 | assert!( | ||
| 161 | (config.vr_select as u8) < ADC_VRSEL, | ||
| 162 | "Reference voltage selection out of bounds" | ||
| 163 | ); | ||
| 164 | // Reset adc | ||
| 165 | self.info.regs.gprcm(0).rstctl().write(|reg| { | ||
| 166 | reg.set_resetstkyclr(true); | ||
| 167 | reg.set_resetassert(true); | ||
| 168 | reg.set_key(vals::ResetKey::KEY); | ||
| 169 | }); | ||
| 170 | |||
| 171 | // Power up adc | ||
| 172 | self.info.regs.gprcm(0).pwren().modify(|reg| { | ||
| 173 | reg.set_enable(true); | ||
| 174 | reg.set_key(vals::PwrenKey::KEY); | ||
| 175 | }); | ||
| 176 | |||
| 177 | // Wait for cycles similar to TI power setup | ||
| 178 | cortex_m::asm::delay(16); | ||
| 179 | |||
| 180 | // Set clock config | ||
| 181 | self.info.regs.gprcm(0).clkcfg().modify(|reg| { | ||
| 182 | reg.set_key(vals::ClkcfgKey::KEY); | ||
| 183 | reg.set_sampclk(vals::Sampclk::SYSOSC); | ||
| 184 | }); | ||
| 185 | self.info.regs.ctl0().modify(|reg| { | ||
| 186 | reg.set_sclkdiv(vals::Sclkdiv::DIV_BY_4); | ||
| 187 | }); | ||
| 188 | self.info.regs.clkfreq().modify(|reg| { | ||
| 189 | reg.set_frange(vals::Frange::RANGE24TO32); | ||
| 190 | }); | ||
| 191 | |||
| 192 | // Init single conversion with software trigger and auto sampling | ||
| 193 | // | ||
| 194 | // We use sequence to support sequence operation in the future, but only set up a single | ||
| 195 | // channel | ||
| 196 | self.info.regs.ctl1().modify(|reg| { | ||
| 197 | reg.set_conseq(vals::Conseq::SEQUENCE); | ||
| 198 | reg.set_sampmode(vals::Sampmode::AUTO); | ||
| 199 | reg.set_trigsrc(vals::Trigsrc::SOFTWARE); | ||
| 200 | }); | ||
| 201 | let res = match config.resolution { | ||
| 202 | Resolution::BIT12 => vals::Res::BIT_12, | ||
| 203 | Resolution::BIT10 => vals::Res::BIT_10, | ||
| 204 | Resolution::BIT8 => vals::Res::BIT_8, | ||
| 205 | }; | ||
| 206 | self.info.regs.ctl2().modify(|reg| { | ||
| 207 | // Startadd detemines the channel used in single mode. | ||
| 208 | reg.set_startadd(Self::SINGLE_CHANNEL); | ||
| 209 | reg.set_endadd(Self::SINGLE_CHANNEL); | ||
| 210 | reg.set_res(res); | ||
| 211 | reg.set_df(false); | ||
| 212 | }); | ||
| 213 | |||
| 214 | // Set the sample time used by all channels for now | ||
| 215 | self.info.regs.scomp0().modify(|reg| { | ||
| 216 | reg.set_val(config.sample_time); | ||
| 217 | }); | ||
| 218 | } | ||
| 219 | |||
| 220 | fn setup_blocking_channel(&mut self, channel: &Peri<'d, impl AdcChannel<T>>) { | ||
| 221 | channel.setup(); | ||
| 222 | |||
| 223 | // CTL0.ENC must be 0 to write the MEMCTL register | ||
| 224 | while self.info.regs.ctl0().read().enc() { | ||
| 225 | // Wait until the ADC is not in conversion mode | ||
| 226 | } | ||
| 227 | |||
| 228 | // Conversion mem config | ||
| 229 | let vrsel = vals::Vrsel::from_bits(self.config.vr_select as u8); | ||
| 230 | self.info.regs.memctl(Self::SINGLE_CHANNEL as usize).modify(|reg| { | ||
| 231 | reg.set_chansel(channel.channel()); | ||
| 232 | reg.set_vrsel(vrsel); | ||
| 233 | reg.set_stime(vals::Stime::SEL_SCOMP0); | ||
| 234 | reg.set_avgen(false); | ||
| 235 | reg.set_bcsen(false); | ||
| 236 | reg.set_trig(vals::Trig::AUTO_NEXT); | ||
| 237 | reg.set_wincomp(false); | ||
| 238 | }); | ||
| 239 | self.info.regs.ctl2().modify(|reg| { | ||
| 240 | // Set end address to the number of used channels | ||
| 241 | reg.set_endadd(Self::SINGLE_CHANNEL); | ||
| 242 | }); | ||
| 243 | } | ||
| 244 | |||
| 245 | fn enable_conversion(&mut self) { | ||
| 246 | // Enable conversion | ||
| 247 | self.info.regs.ctl0().modify(|reg| { | ||
| 248 | reg.set_enc(true); | ||
| 249 | }); | ||
| 250 | } | ||
| 251 | |||
| 252 | fn start_conversion(&mut self) { | ||
| 253 | // Start conversion | ||
| 254 | self.info.regs.ctl1().modify(|reg| { | ||
| 255 | reg.set_sc(vals::Sc::START); | ||
| 256 | }); | ||
| 257 | } | ||
| 258 | |||
| 259 | fn conversion_result(&mut self, channel_id: usize) -> u16 { | ||
| 260 | // Read result | ||
| 261 | self.info.regs.memres(channel_id).read().data() | ||
| 262 | } | ||
| 263 | |||
| 264 | /// Read one ADC channel in blocking mode using the config provided at initialization. | ||
| 265 | pub fn blocking_read(&mut self, channel: &Peri<'d, impl AdcChannel<T>>) -> u16 { | ||
| 266 | self.setup_blocking_channel(channel); | ||
| 267 | self.enable_conversion(); | ||
| 268 | self.start_conversion(); | ||
| 269 | |||
| 270 | while self.info.regs.ctl0().read().enc() {} | ||
| 271 | |||
| 272 | self.conversion_result(Self::SINGLE_CHANNEL as usize) | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | impl<'d, T: Instance> Adc<'d, T, Async> { | ||
| 277 | /// Maximum length allowed for [`Self::read_sequence`]. | ||
| 278 | pub const MAX_SEQUENCE_LEN: usize = ADC_MEMCTL as usize; | ||
| 279 | |||
| 280 | async fn wait_for_conversion(&self) { | ||
| 281 | let info = self.info; | ||
| 282 | let state = self.state; | ||
| 283 | poll_fn(move |cx| { | ||
| 284 | state.waker.register(cx.waker()); | ||
| 285 | |||
| 286 | if !info.regs.ctl0().read().enc() { | ||
| 287 | Poll::Ready(()) | ||
| 288 | } else { | ||
| 289 | Poll::Pending | ||
| 290 | } | ||
| 291 | }) | ||
| 292 | .await; | ||
| 293 | } | ||
| 294 | |||
| 295 | fn setup_async_channel(&self, id: usize, channel: &Peri<'d, impl AdcChannel<T>>, vrsel: Vrsel) { | ||
| 296 | let vrsel = vals::Vrsel::from_bits(vrsel as u8); | ||
| 297 | // Conversion mem config | ||
| 298 | self.info.regs.memctl(id).modify(|reg| { | ||
| 299 | reg.set_chansel(channel.channel()); | ||
| 300 | reg.set_vrsel(vrsel); | ||
| 301 | reg.set_stime(vals::Stime::SEL_SCOMP0); | ||
| 302 | reg.set_avgen(false); | ||
| 303 | reg.set_bcsen(false); | ||
| 304 | reg.set_trig(vals::Trig::AUTO_NEXT); | ||
| 305 | reg.set_wincomp(false); | ||
| 306 | }); | ||
| 307 | |||
| 308 | // Clear interrupt status | ||
| 309 | self.info.regs.cpu_int(0).iclr().write(|reg| { | ||
| 310 | reg.set_memresifg(id, true); | ||
| 311 | }); | ||
| 312 | // Enable interrupt | ||
| 313 | self.info.regs.cpu_int(0).imask().modify(|reg| { | ||
| 314 | reg.set_memresifg(id, true); | ||
| 315 | }); | ||
| 316 | } | ||
| 317 | |||
| 318 | /// Read one ADC channel asynchronously using the config provided at initialization. | ||
| 319 | pub async fn read_channel(&mut self, channel: &Peri<'d, impl AdcChannel<T>>) -> u16 { | ||
| 320 | channel.setup(); | ||
| 321 | |||
| 322 | // CTL0.ENC must be 0 to write the MEMCTL register | ||
| 323 | self.wait_for_conversion().await; | ||
| 324 | |||
| 325 | self.info.regs.ctl2().modify(|reg| { | ||
| 326 | // Set end address to the number of used channels | ||
| 327 | reg.set_endadd(Self::SINGLE_CHANNEL); | ||
| 328 | }); | ||
| 329 | |||
| 330 | self.setup_async_channel(Self::SINGLE_CHANNEL as usize, channel, self.config.vr_select); | ||
| 331 | |||
| 332 | self.enable_conversion(); | ||
| 333 | self.start_conversion(); | ||
| 334 | self.wait_for_conversion().await; | ||
| 335 | |||
| 336 | self.conversion_result(Self::SINGLE_CHANNEL as usize) | ||
| 337 | } | ||
| 338 | |||
| 339 | /// Read one or multiple ADC channels using the Vrsel provided per channel. | ||
| 340 | /// | ||
| 341 | /// `sequence` iterator and `readings` must have the same length. | ||
| 342 | /// | ||
| 343 | /// Example | ||
| 344 | /// ```rust,ignore | ||
| 345 | /// use embassy_mspm0::adc::{Adc, AdcChannel, Vrsel}; | ||
| 346 | /// | ||
| 347 | /// let mut adc = Adc::new_async(p.ADC0, adc_config, Irqs); | ||
| 348 | /// let sequence = [(&p.PA22.into(), Vrsel::VddaVssa), (&p.PA20.into(), Vrsel::VddaVssa)]; | ||
| 349 | /// let mut readings = [0u16; 2]; | ||
| 350 | /// | ||
| 351 | /// adc.read_sequence(sequence.into_iter(), &mut readings).await; | ||
| 352 | /// defmt::info!("Measurements: {}", readings); | ||
| 353 | /// ``` | ||
| 354 | pub async fn read_sequence<'a>( | ||
| 355 | &mut self, | ||
| 356 | sequence: impl ExactSizeIterator<Item = (&'a Peri<'d, AnyAdcChannel<T>>, Vrsel)>, | ||
| 357 | readings: &mut [u16], | ||
| 358 | ) where | ||
| 359 | 'd: 'a, | ||
| 360 | { | ||
| 361 | assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); | ||
| 362 | assert!( | ||
| 363 | sequence.len() == readings.len(), | ||
| 364 | "Sequence length must be equal to readings length" | ||
| 365 | ); | ||
| 366 | assert!( | ||
| 367 | sequence.len() <= Self::MAX_SEQUENCE_LEN, | ||
| 368 | "Asynchronous read sequence cannot be more than {} in length", | ||
| 369 | Self::MAX_SEQUENCE_LEN | ||
| 370 | ); | ||
| 371 | |||
| 372 | // CTL0.ENC must be 0 to write the MEMCTL register | ||
| 373 | self.wait_for_conversion().await; | ||
| 374 | |||
| 375 | self.info.regs.ctl2().modify(|reg| { | ||
| 376 | // Set end address to the number of used channels | ||
| 377 | reg.set_endadd((sequence.len() - 1) as u8); | ||
| 378 | }); | ||
| 379 | |||
| 380 | for (i, (channel, vrsel)) in sequence.enumerate() { | ||
| 381 | self.setup_async_channel(i, channel, vrsel); | ||
| 382 | } | ||
| 383 | self.enable_conversion(); | ||
| 384 | self.start_conversion(); | ||
| 385 | self.wait_for_conversion().await; | ||
| 386 | |||
| 387 | for (i, r) in readings.iter_mut().enumerate() { | ||
| 388 | *r = self.conversion_result(i); | ||
| 389 | } | ||
| 390 | } | ||
| 391 | } | ||
| 392 | |||
| 393 | /// Peripheral instance trait. | ||
| 394 | #[allow(private_bounds)] | ||
| 395 | pub trait Instance: PeripheralType + SealedInstance { | ||
| 396 | type Interrupt: crate::interrupt::typelevel::Interrupt; | ||
| 397 | } | ||
| 398 | |||
| 399 | /// Peripheral state. | ||
| 400 | pub(crate) struct State { | ||
| 401 | waker: AtomicWaker, | ||
| 402 | } | ||
| 403 | |||
| 404 | impl State { | ||
| 405 | pub const fn new() -> Self { | ||
| 406 | Self { | ||
| 407 | waker: AtomicWaker::new(), | ||
| 408 | } | ||
| 409 | } | ||
| 410 | } | ||
| 411 | |||
| 412 | /// Peripheral information. | ||
| 413 | pub(crate) struct Info { | ||
| 414 | pub(crate) regs: Regs, | ||
| 415 | pub(crate) interrupt: Interrupt, | ||
| 416 | } | ||
| 417 | |||
| 418 | /// Peripheral instance trait. | ||
| 419 | pub(crate) trait SealedInstance { | ||
| 420 | fn info() -> &'static Info; | ||
| 421 | fn state() -> &'static State; | ||
| 422 | } | ||
| 423 | |||
| 424 | macro_rules! impl_adc_instance { | ||
| 425 | ($instance: ident) => { | ||
| 426 | impl crate::adc::SealedInstance for crate::peripherals::$instance { | ||
| 427 | fn info() -> &'static crate::adc::Info { | ||
| 428 | use crate::adc::Info; | ||
| 429 | use crate::interrupt::typelevel::Interrupt; | ||
| 430 | |||
| 431 | static INFO: Info = Info { | ||
| 432 | regs: crate::pac::$instance, | ||
| 433 | interrupt: crate::interrupt::typelevel::$instance::IRQ, | ||
| 434 | }; | ||
| 435 | &INFO | ||
| 436 | } | ||
| 437 | |||
| 438 | fn state() -> &'static crate::adc::State { | ||
| 439 | use crate::adc::State; | ||
| 440 | |||
| 441 | static STATE: State = State::new(); | ||
| 442 | &STATE | ||
| 443 | } | ||
| 444 | } | ||
| 445 | |||
| 446 | impl crate::adc::Instance for crate::peripherals::$instance { | ||
| 447 | type Interrupt = crate::interrupt::typelevel::$instance; | ||
| 448 | } | ||
| 449 | }; | ||
| 450 | } | ||
| 451 | |||
| 452 | /// A type-erased channel for a given ADC instance. | ||
| 453 | /// | ||
| 454 | /// This is useful in scenarios where you need the ADC channels to have the same type, such as | ||
| 455 | /// storing them in an array. | ||
| 456 | pub struct AnyAdcChannel<T> { | ||
| 457 | pub(crate) channel: u8, | ||
| 458 | pub(crate) _phantom: PhantomData<T>, | ||
| 459 | } | ||
| 460 | |||
| 461 | impl_peripheral!(AnyAdcChannel<T: Instance>); | ||
| 462 | impl<T: Instance> AdcChannel<T> for AnyAdcChannel<T> {} | ||
| 463 | impl<T: Instance> SealedAdcChannel<T> for AnyAdcChannel<T> { | ||
| 464 | fn channel(&self) -> u8 { | ||
| 465 | self.channel | ||
| 466 | } | ||
| 467 | } | ||
| 468 | |||
| 469 | impl<T> AnyAdcChannel<T> { | ||
| 470 | #[allow(unused)] | ||
| 471 | pub(crate) fn get_hw_channel(&self) -> u8 { | ||
| 472 | self.channel | ||
| 473 | } | ||
| 474 | } | ||
| 475 | |||
| 476 | /// ADC channel. | ||
| 477 | #[allow(private_bounds)] | ||
| 478 | pub trait AdcChannel<T>: PeripheralType + Into<AnyAdcChannel<T>> + SealedAdcChannel<T> + Sized {} | ||
| 479 | |||
| 480 | pub(crate) trait SealedAdcChannel<T> { | ||
| 481 | fn setup(&self) {} | ||
| 482 | |||
| 483 | fn channel(&self) -> u8; | ||
| 484 | } | ||
| 485 | |||
| 486 | macro_rules! impl_adc_pin { | ||
| 487 | ($inst: ident, $pin: ident, $ch: expr) => { | ||
| 488 | impl crate::adc::AdcChannel<peripherals::$inst> for crate::peripherals::$pin {} | ||
| 489 | impl crate::adc::SealedAdcChannel<peripherals::$inst> for crate::peripherals::$pin { | ||
| 490 | fn setup(&self) { | ||
| 491 | crate::gpio::SealedPin::set_as_analog(self); | ||
| 492 | } | ||
| 493 | |||
| 494 | fn channel(&self) -> u8 { | ||
| 495 | $ch | ||
| 496 | } | ||
| 497 | } | ||
| 498 | |||
| 499 | impl From<crate::peripherals::$pin> for crate::adc::AnyAdcChannel<peripherals::$inst> { | ||
| 500 | fn from(val: crate::peripherals::$pin) -> Self { | ||
| 501 | crate::adc::SealedAdcChannel::<peripherals::$inst>::setup(&val); | ||
| 502 | |||
| 503 | Self { | ||
| 504 | channel: crate::adc::SealedAdcChannel::<peripherals::$inst>::channel(&val), | ||
| 505 | _phantom: core::marker::PhantomData, | ||
| 506 | } | ||
| 507 | } | ||
| 508 | } | ||
| 509 | }; | ||
| 510 | } | ||
diff --git a/embassy-mspm0/src/lib.rs b/embassy-mspm0/src/lib.rs index 0cb19a379..13f0ce662 100644 --- a/embassy-mspm0/src/lib.rs +++ b/embassy-mspm0/src/lib.rs | |||
| @@ -13,6 +13,7 @@ pub(crate) mod fmt; | |||
| 13 | // This must be declared early as well for | 13 | // This must be declared early as well for |
| 14 | mod macros; | 14 | mod macros; |
| 15 | 15 | ||
| 16 | pub mod adc; | ||
| 16 | pub mod dma; | 17 | pub mod dma; |
| 17 | pub mod gpio; | 18 | pub mod gpio; |
| 18 | pub mod i2c; | 19 | pub mod i2c; |
diff --git a/examples/mspm0g3507/src/bin/adc.rs b/examples/mspm0g3507/src/bin/adc.rs new file mode 100644 index 000000000..ceccc7c02 --- /dev/null +++ b/examples/mspm0g3507/src/bin/adc.rs | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use defmt::*; | ||
| 5 | use embassy_executor::Spawner; | ||
| 6 | use embassy_mspm0::adc::{self, Adc, Vrsel}; | ||
| 7 | use embassy_mspm0::{bind_interrupts, peripherals, Config}; | ||
| 8 | use embassy_time::Timer; | ||
| 9 | use {defmt_rtt as _, panic_halt as _}; | ||
| 10 | |||
| 11 | bind_interrupts!(struct Irqs { | ||
| 12 | ADC0 => adc::InterruptHandler<peripherals::ADC0>; | ||
| 13 | }); | ||
| 14 | |||
| 15 | #[embassy_executor::main] | ||
| 16 | async fn main(_spawner: Spawner) -> ! { | ||
| 17 | info!("Hello world!"); | ||
| 18 | let p = embassy_mspm0::init(Config::default()); | ||
| 19 | |||
| 20 | // Configure adc with sequence 0 to 1 | ||
| 21 | let mut adc = Adc::new_async(p.ADC0, Default::default(), Irqs); | ||
| 22 | let sequence = [(&p.PA22.into(), Vrsel::VddaVssa), (&p.PB20.into(), Vrsel::VddaVssa)]; | ||
| 23 | let mut readings = [0u16; 2]; | ||
| 24 | |||
| 25 | loop { | ||
| 26 | let r = adc.read_channel(&p.PA27).await; | ||
| 27 | info!("Raw adc PA27: {}", r); | ||
| 28 | // With a voltage range of 0-3.3V and a resolution of 12 bits, the raw value can be | ||
| 29 | // approximated to voltage (~0.0008 per step). | ||
| 30 | let mut x = r as u32; | ||
| 31 | x = x * 8; | ||
| 32 | info!("Adc voltage PA27: {},{:#04}", x / 10_000, x % 10_000); | ||
| 33 | // Read a sequence of channels | ||
| 34 | adc.read_sequence(sequence.into_iter(), &mut readings).await; | ||
| 35 | info!("Raw adc sequence: {}", readings); | ||
| 36 | |||
| 37 | Timer::after_millis(400).await; | ||
| 38 | } | ||
| 39 | } | ||
diff --git a/examples/mspm0l1306/src/bin/adc.rs b/examples/mspm0l1306/src/bin/adc.rs new file mode 100644 index 000000000..2806b98cc --- /dev/null +++ b/examples/mspm0l1306/src/bin/adc.rs | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use defmt::*; | ||
| 5 | use embassy_executor::Spawner; | ||
| 6 | use embassy_mspm0::adc::{self, Adc, Vrsel}; | ||
| 7 | use embassy_mspm0::{bind_interrupts, peripherals, Config}; | ||
| 8 | use embassy_time::Timer; | ||
| 9 | use {defmt_rtt as _, panic_halt as _}; | ||
| 10 | |||
| 11 | bind_interrupts!(struct Irqs { | ||
| 12 | ADC0 => adc::InterruptHandler<peripherals::ADC0>; | ||
| 13 | }); | ||
| 14 | |||
| 15 | #[embassy_executor::main] | ||
| 16 | async fn main(_spawner: Spawner) -> ! { | ||
| 17 | info!("Hello world!"); | ||
| 18 | let p = embassy_mspm0::init(Config::default()); | ||
| 19 | |||
| 20 | // Configure adc with sequence 0 to 1 | ||
| 21 | let mut adc = Adc::new_async(p.ADC0, Default::default(), Irqs); | ||
| 22 | let sequence = [(&p.PA22.into(), Vrsel::VddaVssa), (&p.PA20.into(), Vrsel::VddaVssa)]; | ||
| 23 | let mut readings = [0u16; 2]; | ||
| 24 | |||
| 25 | loop { | ||
| 26 | let r = adc.read_channel(&p.PA27).await; | ||
| 27 | info!("Raw adc PA27: {}", r); | ||
| 28 | // With a voltage range of 0-3.3V and a resolution of 12 bits, the raw value can be | ||
| 29 | // approximated to voltage (~0.0008 per step). | ||
| 30 | let mut x = r as u32; | ||
| 31 | x = x * 8; | ||
| 32 | info!("Adc voltage PA27: {},{:#04}", x / 10_000, x % 10_000); | ||
| 33 | // Read a sequence of channels | ||
| 34 | adc.read_sequence(sequence.into_iter(), &mut readings).await; | ||
| 35 | info!("Raw adc sequence: {}", readings); | ||
| 36 | |||
| 37 | Timer::after_millis(400).await; | ||
| 38 | } | ||
| 39 | } | ||
