From 5f50892d8a8e98e082d83de6ef3d36af4b1ed595 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 30 Jun 2024 18:15:28 +0200 Subject: [PATCH] finished basic ADC and DAC HAL --- examples/simple/examples/adc.rs | 70 +++++ examples/simple/examples/blinky.rs | 1 - examples/simple/examples/dac-adc.rs | 78 ++++++ scripts/VA416xx_Series.yaml | 4 +- va416xx-hal/Cargo.toml | 2 + va416xx-hal/src/adc.rs | 402 ++++++++++++++++++++++++++++ va416xx-hal/src/clock.rs | 43 ++- va416xx-hal/src/dac.rs | 157 +++++++++++ va416xx-hal/src/i2c.rs | 17 +- va416xx-hal/src/lib.rs | 2 + va416xx-hal/src/spi.rs | 101 +------ va416xx-hal/src/timer.rs | 4 + va416xx-hal/src/uart.rs | 4 +- va416xx-hal/src/wdt.rs | 7 +- vscode/launch.json | 114 +++++++- vscode/tasks.json | 28 +- 16 files changed, 910 insertions(+), 124 deletions(-) create mode 100644 examples/simple/examples/adc.rs create mode 100644 examples/simple/examples/dac-adc.rs create mode 100644 va416xx-hal/src/adc.rs create mode 100644 va416xx-hal/src/dac.rs diff --git a/examples/simple/examples/adc.rs b/examples/simple/examples/adc.rs new file mode 100644 index 0000000..a8bc5b4 --- /dev/null +++ b/examples/simple/examples/adc.rs @@ -0,0 +1,70 @@ +//! Simple ADC example. +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use embedded_hal::delay::DelayNs; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use simple_examples::peb1; +use va416xx_hal::{ + adc::{Adc, ChannelSelect, ChannelValue, MultiChannelSelect}, + pac, + prelude::*, + timer::CountdownTimer, +}; + +// Quite spammy and disabled by default. +const ENABLE_BUF_PRINTOUT: bool = false; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + rprintln!("VA416xx ADC example"); + + let mut dp = pac::Peripherals::take().unwrap(); + // Use the external clock connected to XTAL_N. + let clocks = dp + .clkgen + .constrain() + .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ) + .freeze(&mut dp.sysconfig) + .unwrap(); + + let adc = Adc::new_with_channel_tag(&mut dp.sysconfig, dp.adc, &clocks); + let mut delay_provider = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks); + let mut read_buf: [ChannelValue; 8] = [ChannelValue::default(); 8]; + loop { + let single_value = adc + .trigger_and_read_single_channel(va416xx_hal::adc::ChannelSelect::AnIn0) + .expect("reading single channel value failed"); + rprintln!("Read single ADC value on channel 0: {:?}", single_value); + let read_num = adc + .sweep_and_read_range(0, 7, &mut read_buf) + .expect("ADC range read failed"); + if ENABLE_BUF_PRINTOUT { + rprintln!("ADC Range Read (0-8) read {} values", read_num); + rprintln!("ADC Range Read (0-8): {:?}", read_buf); + } + assert_eq!(read_num, 8); + for (idx, ch_val) in read_buf.iter().enumerate() { + assert_eq!( + ch_val.channel(), + ChannelSelect::try_from(idx as u8).unwrap() + ); + } + + adc.sweep_and_read_multiselect( + MultiChannelSelect::AnIn0 | MultiChannelSelect::AnIn2 | MultiChannelSelect::TempSensor, + &mut read_buf[0..3], + ) + .expect("ADC multiselect read failed"); + if ENABLE_BUF_PRINTOUT { + rprintln!("ADC Multiselect Read(0, 2 and 10): {:?}", &read_buf[0..3]); + } + assert_eq!(read_buf[0].channel(), ChannelSelect::AnIn0); + assert_eq!(read_buf[1].channel(), ChannelSelect::AnIn2); + assert_eq!(read_buf[2].channel(), ChannelSelect::TempSensor); + delay_provider.delay_ms(500); + } +} diff --git a/examples/simple/examples/blinky.rs b/examples/simple/examples/blinky.rs index 3056748..253bc6f 100644 --- a/examples/simple/examples/blinky.rs +++ b/examples/simple/examples/blinky.rs @@ -16,7 +16,6 @@ fn main() -> ! { let mut dp = pac::Peripherals::take().unwrap(); let portg = PinsG::new(&mut dp.sysconfig, dp.portg); let mut led = portg.pg5.into_readable_push_pull_output(); - //let mut delay = CountDownTimer::new(&mut dp.SYSCONFIG, 50.mhz(), dp.TIM0); loop { cortex_m::asm::delay(2_000_000); led.toggle().ok(); diff --git a/examples/simple/examples/dac-adc.rs b/examples/simple/examples/dac-adc.rs new file mode 100644 index 0000000..786bdcd --- /dev/null +++ b/examples/simple/examples/dac-adc.rs @@ -0,0 +1,78 @@ +//! Simple DAC-ADC example. +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use embedded_hal::delay::DelayNs; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use simple_examples::peb1; +use va416xx_hal::{adc::Adc, dac::Dac, pac, prelude::*, timer::CountdownTimer}; + +const DAC_INCREMENT: u16 = 256; + +#[derive(Debug, PartialEq, Eq)] +pub enum AppMode { + // Measurements on AIN0. + AdcOnly, + // AOUT0. You can use a multi-meter to measure the changing voltage on the pin. + DacOnly, + /// AOUT0 needs to be wired to AIN0. + DacAndAdc, +} + +const APP_MODE: AppMode = AppMode::DacAndAdc; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + rprintln!("VA416xx DAC/ADC example"); + + let mut dp = pac::Peripherals::take().unwrap(); + // Use the external clock connected to XTAL_N. + let clocks = dp + .clkgen + .constrain() + .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ) + .freeze(&mut dp.sysconfig) + .unwrap(); + let mut dac = None; + if APP_MODE == AppMode::DacOnly || APP_MODE == AppMode::DacAndAdc { + dac = Some(Dac::new( + &mut dp.sysconfig, + dp.dac0, + va416xx_hal::dac::DacSettling::Apb2Times100, + &clocks, + )); + } + let mut adc = None; + if APP_MODE == AppMode::AdcOnly || APP_MODE == AppMode::DacAndAdc { + adc = Some(Adc::new(&mut dp.sysconfig, dp.adc, &clocks)); + } + let mut delay_provider = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks); + let mut current_val = 0; + loop { + if let Some(dac) = &dac { + rprintln!("loading DAC with value {}", current_val); + dac.load_and_trigger_manually(current_val) + .expect("loading DAC value failed"); + if current_val + DAC_INCREMENT >= 4096 { + current_val = 0; + } else { + current_val += DAC_INCREMENT; + } + } + if let Some(dac) = &dac { + // This should never block. + nb::block!(dac.is_settled()).unwrap(); + } + if let Some(adc) = &adc { + let ch_value = adc + .trigger_and_read_single_channel(va416xx_hal::adc::ChannelSelect::AnIn0) + .expect("reading ADC channel 0 failed"); + rprintln!("Received channel value {:?}", ch_value); + } + + delay_provider.delay_ms(500); + } +} diff --git a/scripts/VA416xx_Series.yaml b/scripts/VA416xx_Series.yaml index 76e9304..dfcc374 100644 --- a/scripts/VA416xx_Series.yaml +++ b/scripts/VA416xx_Series.yaml @@ -22,7 +22,9 @@ variants: range: start: 0x0 end: 0x40000 - is_boot_memory: true + # is_boot_memory: true + access: + boot: true cores: - main - !Generic diff --git a/va416xx-hal/Cargo.toml b/va416xx-hal/Cargo.toml index 96cb1eb..d1b3c8a 100644 --- a/va416xx-hal/Cargo.toml +++ b/va416xx-hal/Cargo.toml @@ -17,7 +17,9 @@ paste = "1" embedded-hal-nb = "1" embedded-hal = "1" embedded-io = "0.6" +num_enum = { version = "0.7", default-features = false } typenum = "1" +bitflags = "2" defmt = { version = "0.3", optional = true } fugit = "0.3" delegate = "0.12" diff --git a/va416xx-hal/src/adc.rs b/va416xx-hal/src/adc.rs new file mode 100644 index 0000000..e7ed722 --- /dev/null +++ b/va416xx-hal/src/adc.rs @@ -0,0 +1,402 @@ +use core::marker::PhantomData; + +use crate::clock::Clocks; +use crate::pac; +use crate::prelude::*; +use crate::time::Hertz; +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +pub const ADC_MIN_CLK: Hertz = Hertz::from_raw(2_000_000); +pub const ADC_MAX_CLK: Hertz = Hertz::from_raw(12_500_000); + +#[derive(Debug, PartialEq, Eq, Copy, Clone, TryFromPrimitive, IntoPrimitive)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum ChannelSelect { + /// Analogue Input 0 external channel + AnIn0 = 0, + /// Analogue Input 1 external channel + AnIn1 = 1, + /// Analogue Input 2 external channel + AnIn2 = 2, + /// Analogue Input 3 external channel + AnIn3 = 3, + /// Analogue Input 4 external channel + AnIn4 = 4, + /// Analogue Input 5 external channel + AnIn5 = 5, + /// Analogue Input 6 external channel + AnIn6 = 6, + /// Analogue Input 7 external channel + AnIn7 = 7, + /// DAC 0 internal channel + Dac0 = 8, + /// DAC 1 internal channel + Dac1 = 9, + /// Internal temperature sensor + TempSensor = 10, + /// Internal bandgap 1 V reference + Bandgap1V = 11, + /// Internal bandgap 1.5 V reference + Bandgap1_5V = 12, + Avdd1_5 = 13, + Dvdd1_5 = 14, + /// Internally generated Voltage equal to VREFH / 2 + Vrefp5 = 15, +} + +bitflags::bitflags! { + pub struct MultiChannelSelect: u16 { + const AnIn0 = 1; + const AnIn1 = 1 << 1; + const AnIn2 = 1 << 2; + const AnIn3 = 1 << 3; + const AnIn4 = 1 << 4; + const AnIn5 = 1 << 5; + const AnIn6 = 1 << 6; + const AnIn7 = 1 << 7; + const Dac0 = 1 << 8; + const Dac1 = 1 << 9; + const TempSensor = 1 << 10; + const Bandgap1V = 1 << 11; + const Bandgap1_5V = 1 << 12; + const Avdd1_5 = 1 << 13; + const Dvdd1_5 = 1 << 14; + const Vrefp5 = 1 << 15; + } +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AdcEmptyError; +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct InvalidChannelRangeError; + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct BufferTooSmallError; + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AdcRangeReadError { + InvalidChannelRange(InvalidChannelRangeError), + BufferTooSmall(BufferTooSmallError), +} + +impl From for AdcRangeReadError { + fn from(value: InvalidChannelRangeError) -> Self { + AdcRangeReadError::InvalidChannelRange(value) + } +} + +impl From for AdcRangeReadError { + fn from(value: BufferTooSmallError) -> Self { + AdcRangeReadError::BufferTooSmall(value) + } +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ChannelValue { + /// If the channel tag is enabled, this field will contain the determined channel tag. + channel: ChannelSelect, + /// Raw value. + value: u16, +} + +impl Default for ChannelValue { + fn default() -> Self { + Self { + channel: ChannelSelect::AnIn0, + value: Default::default(), + } + } +} + +impl ChannelValue { + #[inline] + pub fn value(&self) -> u16 { + self.value + } + + #[inline] + pub fn channel(&self) -> ChannelSelect { + self.channel + } +} + +pub enum ChannelTagEnabled {} +pub enum ChannelTagDisabled {} + +pub struct Adc { + adc: pac::Adc, + phantom: PhantomData, +} + +impl Adc {} + +impl Adc { + pub fn new(syscfg: &mut pac::Sysconfig, adc: pac::Adc, clocks: &Clocks) -> Self { + Self::generic_new(syscfg, adc, clocks) + } + + pub fn trigger_and_read_single_channel(&self, ch: ChannelSelect) -> Result { + self.generic_trigger_and_read_single_channel(ch) + .map(|v| v & 0xfff) + } + + /// Perform a sweep for a specified range of ADC channels. + /// + /// Returns the number of read values which were written to the passed RX buffer. + pub fn sweep_and_read_range( + &self, + lower_bound_idx: u8, + upper_bound_idx: u8, + rx_buf: &mut [u16], + ) -> Result<(), AdcRangeReadError> { + self.generic_prepare_range_sweep_and_wait_until_ready( + lower_bound_idx, + upper_bound_idx, + rx_buf.len(), + )?; + for i in 0..self.adc.status().read().fifo_entry_cnt().bits() { + rx_buf[i as usize] = self.adc.fifo_data().read().bits() as u16 & 0xfff; + } + Ok(()) + } + + pub fn sweep_and_read_multiselect( + &self, + ch_select: MultiChannelSelect, + rx_buf: &mut [u16], + ) -> Result<(), BufferTooSmallError> { + self.generic_prepare_multiselect_sweep_and_wait_until_ready(ch_select, rx_buf.len())?; + for i in 0..self.adc.status().read().fifo_entry_cnt().bits() { + rx_buf[i as usize] = self.adc.fifo_data().read().bits() as u16 & 0xfff; + } + Ok(()) + } + + pub fn try_read_single_value(&self) -> nb::Result, ()> { + self.generic_try_read_single_value() + .map(|v| v.map(|v| v & 0xfff)) + } +} + +impl Adc { + pub fn new_with_channel_tag( + syscfg: &mut pac::Sysconfig, + adc: pac::Adc, + clocks: &Clocks, + ) -> Self { + let mut adc = Self::generic_new(syscfg, adc, clocks); + adc.enable_channel_tag(); + adc + } + + pub fn trigger_and_read_single_channel( + &self, + ch: ChannelSelect, + ) -> Result { + self.generic_trigger_and_read_single_channel(ch) + .map(|v| self.create_channel_value(v)) + } + + pub fn try_read_single_value(&self) -> nb::Result, ()> { + self.generic_try_read_single_value() + .map(|v| v.map(|v| self.create_channel_value(v))) + } + + /// Perform a sweep for a specified range of ADC channels. + /// + /// Returns the number of read values which were written to the passed RX buffer. + pub fn sweep_and_read_range( + &self, + lower_bound_idx: u8, + upper_bound_idx: u8, + rx_buf: &mut [ChannelValue], + ) -> Result { + self.generic_prepare_range_sweep_and_wait_until_ready( + lower_bound_idx, + upper_bound_idx, + rx_buf.len(), + )?; + let fifo_entry_count = self.adc.status().read().fifo_entry_cnt().bits(); + for i in 0..core::cmp::min(fifo_entry_count, rx_buf.len() as u8) { + rx_buf[i as usize] = + self.create_channel_value(self.adc.fifo_data().read().bits() as u16); + } + Ok(fifo_entry_count as usize) + } + + pub fn sweep_and_read_multiselect( + &self, + ch_select: MultiChannelSelect, + rx_buf: &mut [ChannelValue], + ) -> Result<(), BufferTooSmallError> { + self.generic_prepare_multiselect_sweep_and_wait_until_ready(ch_select, rx_buf.len())?; + for i in 0..self.adc.status().read().fifo_entry_cnt().bits() { + rx_buf[i as usize] = + self.create_channel_value(self.adc.fifo_data().read().bits() as u16); + } + Ok(()) + } + + #[inline] + pub fn create_channel_value(&self, raw_value: u16) -> ChannelValue { + ChannelValue { + value: raw_value & 0xfff, + channel: ChannelSelect::try_from(((raw_value >> 12) & 0xf) as u8).unwrap(), + } + } +} + +impl Adc { + fn generic_new(syscfg: &mut pac::Sysconfig, adc: pac::Adc, _clocks: &Clocks) -> Self { + syscfg.enable_peripheral_clock(crate::clock::PeripheralSelect::Adc); + adc.ctrl().write(|w| unsafe { w.bits(0) }); + let adc = Self { + adc, + phantom: PhantomData, + }; + adc.clear_fifo(); + adc + } + + #[inline(always)] + fn enable_channel_tag(&mut self) { + self.adc.ctrl().modify(|_, w| w.chan_tag_en().set_bit()); + } + + #[inline(always)] + fn disable_channel_tag(&mut self) { + self.adc.ctrl().modify(|_, w| w.chan_tag_en().clear_bit()); + } + + #[inline(always)] + pub fn channel_tag_enabled(&self) -> bool { + self.adc.ctrl().read().chan_tag_en().bit_is_set() + } + + #[inline(always)] + pub fn clear_fifo(&self) { + self.adc.fifo_clr().write(|w| unsafe { w.bits(1) }); + } + + pub fn generic_try_read_single_value(&self) -> nb::Result, ()> { + if self.adc.status().read().adc_busy().bit_is_set() { + return Err(nb::Error::WouldBlock); + } + if self.adc.status().read().fifo_entry_cnt().bits() == 0 { + return Ok(None); + } + Ok(Some(self.adc.fifo_data().read().bits() as u16)) + } + + fn generic_trigger_single_channel(&self, ch: ChannelSelect) { + self.adc.ctrl().modify(|_, w| { + w.ext_trig_en().clear_bit(); + unsafe { + // N + 1 conversions, so set set 0 here. + w.conv_cnt().bits(0); + w.chan_en().bits(1 << ch as u8) + } + }); + self.clear_fifo(); + + self.adc.ctrl().modify(|_, w| w.manual_trig().set_bit()); + } + + fn generic_prepare_range_sweep_and_wait_until_ready( + &self, + lower_bound_idx: u8, + upper_bound_idx: u8, + buf_len: usize, + ) -> Result<(), AdcRangeReadError> { + if (lower_bound_idx > 15 || upper_bound_idx > 15) || lower_bound_idx > upper_bound_idx { + return Err(InvalidChannelRangeError.into()); + } + let ch_count = upper_bound_idx - lower_bound_idx + 1; + if buf_len < ch_count as usize { + return Err(BufferTooSmallError.into()); + } + let mut ch_select = 0; + for i in lower_bound_idx..upper_bound_idx + 1 { + ch_select |= 1 << i; + } + self.generic_trigger_sweep(ch_select); + cortex_m::asm::nop(); + cortex_m::asm::nop(); + while self.adc.status().read().adc_busy().bit_is_set() { + cortex_m::asm::nop(); + } + Ok(()) + } + + fn generic_prepare_multiselect_sweep_and_wait_until_ready( + &self, + ch_select: MultiChannelSelect, + buf_len: usize, + ) -> Result<(), BufferTooSmallError> { + let ch_select = ch_select.bits(); + let ch_count = ch_select.count_ones(); + if buf_len < ch_count as usize { + return Err(BufferTooSmallError); + } + self.generic_trigger_sweep(ch_select); + while self.adc.status().read().adc_busy().bit_is_set() { + cortex_m::asm::nop(); + } + Ok(()) + } + + fn generic_trigger_sweep(&self, ch_select: u16) { + let ch_num = ch_select.count_ones() as u8; + assert!(ch_num > 0); + self.adc.ctrl().modify(|_, w| { + w.ext_trig_en().clear_bit(); + unsafe { + // N + 1 conversions. + w.conv_cnt().bits(0); + w.chan_en().bits(ch_select); + w.sweep_en().set_bit() + } + }); + self.clear_fifo(); + + self.adc.ctrl().modify(|_, w| w.manual_trig().set_bit()); + } + + fn generic_trigger_and_read_single_channel( + &self, + ch: ChannelSelect, + ) -> Result { + self.generic_trigger_single_channel(ch); + nb::block!(self.generic_try_read_single_value()) + .unwrap() + .ok_or(AdcEmptyError) + } +} + +impl From> for Adc { + fn from(value: Adc) -> Self { + let mut adc = Self { + adc: value.adc, + phantom: PhantomData, + }; + adc.enable_channel_tag(); + adc + } +} + +impl From> for Adc { + fn from(value: Adc) -> Self { + let mut adc = Self { + adc: value.adc, + phantom: PhantomData, + }; + adc.disable_channel_tag(); + adc + } +} diff --git a/va416xx-hal/src/clock.rs b/va416xx-hal/src/clock.rs index 81468a2..1ec4e7e 100644 --- a/va416xx-hal/src/clock.rs +++ b/va416xx-hal/src/clock.rs @@ -10,6 +10,7 @@ //! # Examples //! //! - [UART example on the PEB1 board](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs) +use crate::adc::ADC_MAX_CLK; use crate::pac; use crate::time::Hertz; @@ -52,7 +53,7 @@ pub enum PeripheralSelect { PortG = 30, } -pub type PeripheralClocks = PeripheralSelect; +pub type PeripheralClock = PeripheralSelect; #[derive(Debug, PartialEq, Eq)] pub enum FilterClkSel { @@ -94,24 +95,34 @@ pub fn deassert_periph_reset(syscfg: &mut pac::Sysconfig, periph: PeripheralSele .modify(|r, w| unsafe { w.bits(r.bits() | (1 << periph as u8)) }); } +#[inline(always)] +fn assert_periph_reset_for_two_cycles(syscfg: &mut pac::Sysconfig, periph: PeripheralSelect) { + assert_periph_reset(syscfg, periph); + cortex_m::asm::nop(); + cortex_m::asm::nop(); + deassert_periph_reset(syscfg, periph); +} + pub trait SyscfgExt { - fn enable_peripheral_clock(&mut self, clock: PeripheralClocks); + fn enable_peripheral_clock(&mut self, clock: PeripheralClock); - fn disable_peripheral_clock(&mut self, clock: PeripheralClocks); + fn disable_peripheral_clock(&mut self, clock: PeripheralClock); - fn assert_periph_reset(&mut self, clock: PeripheralSelect); + fn assert_periph_reset(&mut self, periph: PeripheralSelect); - fn deassert_periph_reset(&mut self, clock: PeripheralSelect); + fn deassert_periph_reset(&mut self, periph: PeripheralSelect); + + fn assert_periph_reset_for_two_cycles(&mut self, periph: PeripheralSelect); } impl SyscfgExt for pac::Sysconfig { #[inline(always)] - fn enable_peripheral_clock(&mut self, clock: PeripheralClocks) { + fn enable_peripheral_clock(&mut self, clock: PeripheralClock) { enable_peripheral_clock(self, clock) } #[inline(always)] - fn disable_peripheral_clock(&mut self, clock: PeripheralClocks) { + fn disable_peripheral_clock(&mut self, clock: PeripheralClock) { disable_peripheral_clock(self, clock) } @@ -124,6 +135,11 @@ impl SyscfgExt for pac::Sysconfig { fn deassert_periph_reset(&mut self, clock: PeripheralSelect) { deassert_periph_reset(self, clock) } + + #[inline(always)] + fn assert_periph_reset_for_two_cycles(&mut self, periph: PeripheralSelect) { + assert_periph_reset_for_two_cycles(self, periph) + } } /// Refer to chapter 8 (p.57) of the programmers guide for detailed information. @@ -435,20 +451,23 @@ impl ClkgenCfgr { // ADC clock (must be 2-12.5 MHz) // NOTE: Not using divide by 1 or /2 ratio in REVA silicon because of triggering issue // For this reason, keep SYSCLK above 8MHz to have the ADC /4 ratio in range) - if final_sysclk.raw() <= 50_000_000 { + let adc_clk = if final_sysclk.raw() <= ADC_MAX_CLK.raw() * 4 { self.clkgen .ctrl1() .modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div4 as u8) }); + final_sysclk / 4 } else { self.clkgen .ctrl1() .modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div8 as u8) }); - } + final_sysclk / 8 + }; Ok(Clocks { sysclk: final_sysclk, apb1: final_sysclk / 2, apb2: final_sysclk / 4, + adc_clk, }) } } @@ -464,6 +483,7 @@ pub struct Clocks { sysclk: Hertz, apb1: Hertz, apb2: Hertz, + adc_clk: Hertz, } impl Clocks { @@ -491,6 +511,11 @@ impl Clocks { pub fn sysclk(&self) -> Hertz { self.sysclk } + + /// Returns the ADC clock frequency which has a separate divider. + pub fn adc_clk(&self) -> Hertz { + self.adc_clk + } } pub fn rearm_sysclk_lost() { diff --git a/va416xx-hal/src/dac.rs b/va416xx-hal/src/dac.rs new file mode 100644 index 0000000..d5aaa9f --- /dev/null +++ b/va416xx-hal/src/dac.rs @@ -0,0 +1,157 @@ +use core::ops::Deref; + +use crate::{ + clock::{Clocks, PeripheralSelect, SyscfgExt}, + pac, +}; + +pub type DacRegisterBlock = pac::dac0::RegisterBlock; + +/// Common trait implemented by all PAC peripheral access structures. The register block +/// format is the same for all DAC blocks. +pub trait Instance: Deref { + const IDX: u8; + + fn ptr() -> *const DacRegisterBlock; +} + +impl Instance for pac::Dac0 { + const IDX: u8 = 0; + + #[inline(always)] + fn ptr() -> *const DacRegisterBlock { + Self::ptr() + } +} + +impl Instance for pac::Dac1 { + const IDX: u8 = 1; + + #[inline(always)] + fn ptr() -> *const DacRegisterBlock { + Self::ptr() + } +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DacSettling { + NoSettling = 0, + Apb2Times25 = 1, + Apb2Times50 = 2, + Apb2Times75 = 3, + Apb2Times100 = 4, + Apb2Times125 = 5, + Apb2Times150 = 6, +} + +pub struct Dac { + dac: DacInstance, +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ValueTooLarge; + +impl Dac { + /// Create a new [Dac] driver instance. + /// + /// The [Clocks] structure is expected here as well to ensure the clock was set up properly. + pub fn new( + syscfg: &mut pac::Sysconfig, + dac: DacInstance, + dac_settling: DacSettling, + _clocks: &Clocks, + ) -> Self { + syscfg.enable_peripheral_clock(PeripheralSelect::Dac); + + dac.ctrl1().write(|w| { + w.dac_en().set_bit(); + // SAFETY: Enum values are valid values only. + unsafe { w.dac_settling().bits(dac_settling as u8) } + }); + let dac = Self { dac }; + dac.clear_fifo(); + dac.clear_irqs(); + dac + } + + #[inline(always)] + pub fn clear_irqs(&self) { + self.dac.irq_clr().write(|w| { + w.fifo_oflow().set_bit(); + w.fifo_uflow().set_bit(); + w.dac_done().set_bit(); + w.trig_error().set_bit() + }); + } + + #[inline(always)] + pub fn clear_fifo(&self) { + self.dac.fifo_clr().write(|w| unsafe { w.bits(1) }); + } + + /// Load next value into the FIFO. + /// + /// Uses the [nb] API to allow blocking and non-blocking usage. + #[inline(always)] + pub fn load_value(&self, val: u16) -> nb::Result<(), ValueTooLarge> { + if val > 2_u16.pow(12) - 1 { + return Err(nb::Error::Other(ValueTooLarge)); + } + if self.dac.status().read().fifo_entry_cnt().bits() >= 32_u8 { + return Err(nb::Error::WouldBlock); + } + self.dac + .fifo_data() + .write(|w| unsafe { w.bits(val.into()) }); + Ok(()) + } + + /// This loads and triggers the next value immediately. It also clears the FIFO before + /// loading the passed value. + #[inline(always)] + pub fn load_and_trigger_manually(&self, val: u16) -> Result<(), ValueTooLarge> { + if val > 2_u16.pow(12) - 1 { + return Err(ValueTooLarge); + } + self.clear_fifo(); + // This should never block, the FIFO was cleared. We checked the value as well, so unwrap + // is okay here. + nb::block!(self.load_value(val)).unwrap(); + self.trigger_manually(); + Ok(()) + } + + /// Manually trigger the DAC. This will de-queue the next value inside the FIFO + /// to be processed by the DAC. + #[inline(always)] + pub fn trigger_manually(&self) { + self.dac.ctrl0().write(|w| w.man_trig_en().set_bit()); + } + + #[inline(always)] + pub fn enable_external_trigger(&self) { + self.dac.ctrl0().write(|w| w.ext_trig_en().set_bit()); + } + + pub fn is_settled(&self) -> nb::Result<(), ()> { + if self.dac.status().read().dac_busy().bit_is_set() { + return Err(nb::Error::WouldBlock); + } + Ok(()) + } + + #[inline(always)] + pub fn reset(&mut self, syscfg: &mut pac::Sysconfig) { + syscfg.enable_peripheral_clock(PeripheralSelect::Dac); + syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Dac); + } + + /// Relases the DAC, which also disables its peripheral clock. + #[inline(always)] + pub fn release(self, syscfg: &mut pac::Sysconfig) -> DacInstance { + syscfg.disable_peripheral_clock(PeripheralSelect::Dac); + self.dac + } +} diff --git a/va416xx-hal/src/i2c.rs b/va416xx-hal/src/i2c.rs index ae7b42d..715997c 100644 --- a/va416xx-hal/src/i2c.rs +++ b/va416xx-hal/src/i2c.rs @@ -4,11 +4,9 @@ //! //! - [PEB1 accelerometer example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/peb1-accelerometer.rs) use crate::{ - clock::{ - assert_periph_reset, deassert_periph_reset, enable_peripheral_clock, Clocks, - PeripheralSelect, - }, + clock::{Clocks, PeripheralSelect}, pac, + prelude::SyscfgExt, time::Hertz, typelevel::Sealed, }; @@ -125,6 +123,7 @@ impl Instance for pac::I2c0 { const IDX: u8 = 0; const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c0; + #[inline(always)] fn ptr() -> *const I2cRegBlock { Self::ptr() } @@ -134,6 +133,7 @@ impl Instance for pac::I2c1 { const IDX: u8 = 1; const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c1; + #[inline(always)] fn ptr() -> *const I2cRegBlock { Self::ptr() } @@ -143,6 +143,7 @@ impl Instance for pac::I2c2 { const IDX: u8 = 2; const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c2; + #[inline(always)] fn ptr() -> *const I2cRegBlock { Self::ptr() } @@ -312,17 +313,13 @@ impl I2cBase { impl I2cBase { pub fn new( i2c: I2c, - sys_cfg: &mut pac::Sysconfig, + syscfg: &mut pac::Sysconfig, clocks: &Clocks, speed_mode: I2cSpeed, ms_cfg: Option<&MasterConfig>, sl_cfg: Option<&SlaveConfig>, ) -> Result { - enable_peripheral_clock(sys_cfg, I2c::PERIPH_SEL); - assert_periph_reset(sys_cfg, I2c::PERIPH_SEL); - cortex_m::asm::nop(); - cortex_m::asm::nop(); - deassert_periph_reset(sys_cfg, I2c::PERIPH_SEL); + syscfg.enable_peripheral_clock(I2c::PERIPH_SEL); let mut i2c_base = I2cBase { i2c, diff --git a/va416xx-hal/src/lib.rs b/va416xx-hal/src/lib.rs index ec5521d..b515503 100644 --- a/va416xx-hal/src/lib.rs +++ b/va416xx-hal/src/lib.rs @@ -8,7 +8,9 @@ pub use va416xx as pac; pub mod prelude; +pub mod adc; pub mod clock; +pub mod dac; pub mod gpio; pub mod i2c; pub mod pwm; diff --git a/va416xx-hal/src/spi.rs b/va416xx-hal/src/spi.rs index cc1acb3..81dbeb5 100644 --- a/va416xx-hal/src/spi.rs +++ b/va416xx-hal/src/spi.rs @@ -8,7 +8,7 @@ use core::{convert::Infallible, marker::PhantomData, ops::Deref}; use embedded_hal::spi::Mode; use crate::{ - clock::PeripheralSelect, + clock::{PeripheralSelect, SyscfgExt}, gpio::{ AltFunc1, AltFunc2, AltFunc3, Pin, PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, PB0, PB1, PB10, PB11, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PB5, PB6, PB7, PB8, PB9, PC0, PC1, @@ -365,6 +365,7 @@ impl Instance for pac::Spi0 { const IDX: u8 = 0; const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi0; + #[inline(always)] fn ptr() -> *const SpiRegBlock { Self::ptr() } @@ -374,6 +375,7 @@ impl Instance for pac::Spi1 { const IDX: u8 = 1; const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi1; + #[inline(always)] fn ptr() -> *const SpiRegBlock { Self::ptr() } @@ -383,6 +385,7 @@ impl Instance for pac::Spi2 { const IDX: u8 = 2; const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi2; + #[inline(always)] fn ptr() -> *const SpiRegBlock { Self::ptr() } @@ -472,6 +475,7 @@ where self.cfg_hw_cs(HwCs::CS_ID); } + #[inline] pub fn cfg_hw_cs_disable(&mut self) { self.spi.ctrl1().modify(|_, w| { w.sod().set_bit(); @@ -571,99 +575,6 @@ where } } -/* -macro_rules! spi_ctor { - ($spiI:ident, $PeriphSel: path) => { - /// Create a new SPI struct - /// - /// You can delete the pin type information by calling the - /// [`downgrade`](Self::downgrade) function - /// - /// ## Arguments - /// * `spi` - SPI bus to use - /// * `pins` - Pins to be used for SPI transactions. These pins are consumed - /// to ensure the pins can not be used for other purposes anymore - /// * `spi_cfg` - Configuration specific to the SPI bus - /// * `transfer_cfg` - Optional initial transfer configuration which includes - /// configuration which can change across individual SPI transfers like SPI mode - /// or SPI clock. If only one device is connected, this configuration only needs - /// to be done once. - /// * `syscfg` - Can be passed optionally to enable the peripheral clock - pub fn $spiI( - spi: SpiI, - pins: (Sck, Miso, Mosi), - clocks: &crate::clock::Clocks, - spi_cfg: SpiConfig, - syscfg: &mut pac::Sysconfig, - transfer_cfg: Option<&ErasedTransferConfig>, - ) -> Self { - crate::clock::enable_peripheral_clock(syscfg, $PeriphSel); - let SpiConfig { - ser_clock_rate_div, - ms, - slave_output_disable, - loopback_mode, - master_delayer_capture, - } = spi_cfg; - let mut mode = embedded_hal::spi::MODE_0; - let mut clk_prescale = 0x02; - let mut ss = 0; - let mut init_blockmode = false; - let apb1_clk = clocks.apb1(); - if let Some(transfer_cfg) = transfer_cfg { - mode = transfer_cfg.mode; - clk_prescale = - apb1_clk.raw() / (transfer_cfg.spi_clk.raw() * (ser_clock_rate_div as u32 + 1)); - if transfer_cfg.hw_cs != HwChipSelectId::Invalid { - ss = transfer_cfg.hw_cs as u8; - } - init_blockmode = transfer_cfg.blockmode; - } - - let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode); - spi.ctrl0().write(|w| { - unsafe { - w.size().bits(Word::word_reg()); - w.scrdv().bits(ser_clock_rate_div); - // Clear clock phase and polarity. Will be set to correct value for each - // transfer - w.spo().bit(cpo_bit); - w.sph().bit(cph_bit) - } - }); - spi.ctrl1().write(|w| { - w.lbm().bit(loopback_mode); - w.sod().bit(slave_output_disable); - w.ms().bit(ms); - w.mdlycap().bit(master_delayer_capture); - w.blockmode().bit(init_blockmode); - unsafe { w.ss().bits(ss) } - }); - - spi.fifo_clr().write(|w| { - w.rxfifo().set_bit(); - w.txfifo().set_bit() - }); - spi.clkprescale().write(|w| unsafe { w.bits(clk_prescale) }); - // Enable the peripheral as the last step as recommended in the - // programmers guide - spi.ctrl1().modify(|_, w| w.enable().set_bit()); - Spi { - inner: SpiBase { - spi, - cfg: spi_cfg, - apb1_clk, - fill_word: Default::default(), - blockmode: init_blockmode, - word: PhantomData, - }, - pins, - } - } - }; -} -*/ - impl< SpiI: Instance, Sck: PinSck, @@ -698,6 +609,8 @@ where transfer_cfg: Option<&ErasedTransferConfig>, ) -> Self { crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL); + // This is done in the C HAL. + syscfg.assert_periph_reset_for_two_cycles(SpiI::PERIPH_SEL); let SpiConfig { ser_clock_rate_div, ms, diff --git a/va416xx-hal/src/timer.rs b/va416xx-hal/src/timer.rs index e3831b8..4438e05 100644 --- a/va416xx-hal/src/timer.rs +++ b/va416xx-hal/src/timer.rs @@ -513,6 +513,7 @@ impl CountdownTimer { /// Listen for events. Depending on the IRQ configuration, this also activates the IRQ in the /// IRQSEL peripheral for the provided interrupt and unmasks the interrupt + #[inline] pub fn listen(&mut self) { self.listening = true; self.enable_interrupt(); @@ -532,10 +533,12 @@ impl CountdownTimer { } } + #[inline] pub fn stop(&mut self) { self.tim.reg().ctrl().write(|w| w.enable().clear_bit()); } + #[inline] pub fn unlisten(&mut self) { self.listening = true; self.disable_interrupt(); @@ -552,6 +555,7 @@ impl CountdownTimer { self.tim.reg().ctrl().modify(|_, w| w.irq_enb().clear_bit()); } + #[inline] pub fn release(self, syscfg: &mut pac::Sysconfig) -> Tim { self.tim.reg().ctrl().write(|w| w.enable().clear_bit()); syscfg diff --git a/va416xx-hal/src/uart.rs b/va416xx-hal/src/uart.rs index d9b8cf4..8f75201 100644 --- a/va416xx-hal/src/uart.rs +++ b/va416xx-hal/src/uart.rs @@ -9,7 +9,7 @@ use core::ops::Deref; use embedded_hal_nb::serial::Read; use fugit::RateExtU32; -use crate::clock::{Clocks, PeripheralSelect}; +use crate::clock::{Clocks, PeripheralSelect, SyscfgExt}; use crate::gpio::{AltFunc1, Pin, PD11, PD12, PE2, PE3, PF11, PF12, PF8, PF9, PG0, PG1}; use crate::time::Hertz; use crate::{disable_interrupt, enable_interrupt}; @@ -520,6 +520,8 @@ impl Uart { clocks: &Clocks, ) -> Self { crate::clock::enable_peripheral_clock(syscfg, UartInstance::PERIPH_SEL); + // This is done in the C HAL. + syscfg.assert_periph_reset_for_two_cycles(UartInstance::PERIPH_SEL); Uart { inner: UartBase { uart, diff --git a/va416xx-hal/src/wdt.rs b/va416xx-hal/src/wdt.rs index c5594b3..2447954 100644 --- a/va416xx-hal/src/wdt.rs +++ b/va416xx-hal/src/wdt.rs @@ -51,12 +51,7 @@ impl WdtController { wdt_freq_ms: u32, ) -> Self { syscfg.enable_peripheral_clock(PeripheralSelect::Watchdog); - // It's done like that in Vorago examples. Not exactly sure why the reset is necessary - // though.. - syscfg.assert_periph_reset(PeripheralSelect::Watchdog); - cortex_m::asm::nop(); - cortex_m::asm::nop(); - syscfg.deassert_periph_reset(PeripheralSelect::Watchdog); + syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Watchdog); let wdt_clock = clocks.apb2(); let mut wdt_ctrl = Self { diff --git a/vscode/launch.json b/vscode/launch.json index c2763cc..7d5f4de 100644 --- a/vscode/launch.json +++ b/vscode/launch.json @@ -4,6 +4,56 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "preLaunchTask": "blinky-example", + "type": "probe-rs-debug", + "request": "launch", + "name": "probe-rs Debug Blinky", + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": true + }, + "chip": "VA416xx", + "coreConfigs": [ + { + "programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/blinky", + "rttEnabled": true, + "svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched" + } + ] + }, + { + "preLaunchTask": "rtt-log-example", + "type": "probe-rs-debug", + "request": "launch", + "name": "probe-rs Debug RTT", + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": false + }, + "chip": "VA416xx", + "coreConfigs": [ + { + "programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/rtt-log", + "rttEnabled": true, + "svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched" + } + ] + }, + { + "preLaunchTask": "rtt-log-example", + "type": "probe-rs-debug", + "request": "attach", + "name": "probe-rs Attach RTT", + "chip": "VA416xx", + "coreConfigs": [ + { + "programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/rtt-log", + "rttEnabled": true, + "svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd" + } + ] + }, { "type": "cortex-debug", "request": "launch", @@ -19,7 +69,7 @@ "monitor reset", "load", ], - "executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/blinky-hal", + "executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/blinky", "interface": "swd", "runToEntryPoint": "main", "rttConfig": { @@ -185,5 +235,67 @@ ] } }, + { + "type": "cortex-debug", + "request": "launch", + "name": "Debug DAC/ADC Example", + "servertype": "jlink", + "jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript", + "cwd": "${workspaceRoot}", + "device": "Cortex-M4", + "svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched", + "preLaunchTask": "dac-adc-example", + "overrideLaunchCommands": [ + "monitor halt", + "monitor reset", + "load", + ], + "executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/dac-adc", + "interface": "swd", + "runToEntryPoint": "main", + "rttConfig": { + "enabled": true, + // Have to use exact address unfortunately. "auto" does not work for some reason.. + "address": "0x1fff8000", + "decoders": [ + { + "port": 0, + "timestamp": true, + "type": "console" + } + ] + } + }, + { + "type": "cortex-debug", + "request": "launch", + "name": "Debug ADC Example", + "servertype": "jlink", + "jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript", + "cwd": "${workspaceRoot}", + "device": "Cortex-M4", + "svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched", + "preLaunchTask": "adc-example", + "overrideLaunchCommands": [ + "monitor halt", + "monitor reset", + "load", + ], + "executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/examples/adc", + "interface": "swd", + "runToEntryPoint": "main", + "rttConfig": { + "enabled": true, + // Have to use exact address unfortunately. "auto" does not work for some reason.. + "address": "0x1fff8000", + "decoders": [ + { + "port": 0, + "timestamp": true, + "type": "console" + } + ] + } + }, ] } \ No newline at end of file diff --git a/vscode/tasks.json b/vscode/tasks.json index 3474f1f..bf7bbd9 100644 --- a/vscode/tasks.json +++ b/vscode/tasks.json @@ -36,7 +36,7 @@ "args": [ "build", "--example", - "blinky-hal" + "blinky" ], "group": { "kind": "build", @@ -82,5 +82,31 @@ "kind": "build", } }, + { + "label": "dac-adc-example", + "type": "shell", + "command": "~/.cargo/bin/cargo", // note: full path to the cargo + "args": [ + "build", + "--example", + "dac-adc" + ], + "group": { + "kind": "build", + } + }, + { + "label": "adc-example", + "type": "shell", + "command": "~/.cargo/bin/cargo", // note: full path to the cargo + "args": [ + "build", + "--example", + "adc" + ], + "group": { + "kind": "build", + } + }, ] } \ No newline at end of file