Compare commits
1 Commits
0df05d8fba
...
afa2849fa7
Author | SHA1 | Date | |
---|---|---|---|
afa2849fa7 |
@@ -6,8 +6,6 @@
|
||||
#![no_std]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use core::cell::RefCell;
|
||||
use cortex_m::interrupt::Mutex;
|
||||
use cortex_m_rt::entry;
|
||||
use embedded_hal::delay::DelayNs;
|
||||
// Import panic provider.
|
||||
@@ -17,12 +15,9 @@ use defmt_rtt as _;
|
||||
use va108xx_hal::{
|
||||
pac::{self, interrupt},
|
||||
prelude::*,
|
||||
timer::{CascadeCtrl, CascadeSource, CountdownTimer, InterruptConfig},
|
||||
timer::{CascadeControl, CascadeSelect, CascadeSource, CountdownTimer, InterruptConfig},
|
||||
};
|
||||
|
||||
static CSD_TGT_1: Mutex<RefCell<Option<CountdownTimer>>> = Mutex::new(RefCell::new(None));
|
||||
static CSD_TGT_2: Mutex<RefCell<Option<CountdownTimer>>> = Mutex::new(RefCell::new(None));
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
defmt::println!("-- VA108xx Cascade example application--");
|
||||
@@ -31,21 +26,22 @@ fn main() -> ! {
|
||||
let mut delay = CountdownTimer::new(50.MHz(), dp.tim0);
|
||||
|
||||
// Will be started periodically to trigger a cascade
|
||||
let mut cascade_triggerer = CountdownTimer::new(50.MHz(), dp.tim3).auto_disable(true);
|
||||
let mut cascade_triggerer = CountdownTimer::new(50.MHz(), dp.tim3);
|
||||
cascade_triggerer.auto_disable(true);
|
||||
cascade_triggerer.enable_interrupt(InterruptConfig::new(pac::Interrupt::OC1, true, false));
|
||||
cascade_triggerer.enable();
|
||||
|
||||
// First target for cascade
|
||||
let mut cascade_target_1 = CountdownTimer::new(50.MHz(), dp.tim4).auto_deactivate(true);
|
||||
let mut cascade_target_1 = CountdownTimer::new(50.MHz(), dp.tim4);
|
||||
cascade_target_1.auto_deactivate(true);
|
||||
cascade_target_1
|
||||
.cascade_0_source(CascadeSource::Tim(3))
|
||||
.expect("Configuring cascade source for TIM4 failed");
|
||||
let mut csd_cfg = CascadeCtrl {
|
||||
enb_start_src_csd0: true,
|
||||
.cascade_source(CascadeSelect::Csd0, CascadeSource::Tim(3))
|
||||
.unwrap();
|
||||
let mut csd_cfg = CascadeControl {
|
||||
enable_src_0: true,
|
||||
trigger_mode_0: true,
|
||||
..Default::default()
|
||||
};
|
||||
// Use trigger mode here
|
||||
csd_cfg.trg_csd0 = true;
|
||||
cascade_target_1.cascade_control(csd_cfg);
|
||||
// Normally it should already be sufficient to activate IRQ in the CTRL
|
||||
// register but a full interrupt is use here to display print output when
|
||||
@@ -56,16 +52,17 @@ fn main() -> ! {
|
||||
cascade_target_1.start(1.Hz());
|
||||
|
||||
// Activated by first cascade target
|
||||
let mut cascade_target_2 = CountdownTimer::new(50.MHz(), dp.tim5).auto_deactivate(true);
|
||||
let mut cascade_target_2 = CountdownTimer::new(50.MHz(), dp.tim5);
|
||||
cascade_target_2.auto_deactivate(true);
|
||||
// Set TIM4 as cascade source
|
||||
cascade_target_2
|
||||
.cascade_1_source(CascadeSource::Tim(4))
|
||||
.expect("Configuring cascade source for TIM5 failed");
|
||||
.cascade_source(CascadeSelect::Csd1, CascadeSource::Tim(4))
|
||||
.unwrap();
|
||||
|
||||
csd_cfg = CascadeCtrl::default();
|
||||
csd_cfg.enb_start_src_csd1 = true;
|
||||
csd_cfg = CascadeControl::default();
|
||||
csd_cfg.enable_src_1 = true;
|
||||
// Use trigger mode here
|
||||
csd_cfg.trg_csd1 = true;
|
||||
csd_cfg.trigger_mode_1 = true;
|
||||
cascade_target_2.cascade_control(csd_cfg);
|
||||
// Normally it should already be sufficient to activate IRQ in the CTRL
|
||||
// register but a full interrupt is use here to display print output when
|
||||
@@ -82,11 +79,7 @@ fn main() -> ! {
|
||||
cortex_m::peripheral::NVIC::unmask(pac::Interrupt::OC2);
|
||||
cortex_m::peripheral::NVIC::unmask(pac::Interrupt::OC3);
|
||||
}
|
||||
// Make both cascade targets accessible from the IRQ handler with the Mutex dance
|
||||
cortex_m::interrupt::free(|cs| {
|
||||
CSD_TGT_1.borrow(cs).replace(Some(cascade_target_1));
|
||||
CSD_TGT_2.borrow(cs).replace(Some(cascade_target_2));
|
||||
});
|
||||
|
||||
loop {
|
||||
defmt::info!("-- Triggering cascade in 0.5 seconds --");
|
||||
cascade_triggerer.start(2.Hz());
|
||||
@@ -97,7 +90,7 @@ fn main() -> ! {
|
||||
#[interrupt]
|
||||
fn OC1() {
|
||||
static mut IDX: u32 = 0;
|
||||
defmt::info!("{}: Cascade triggered timed out", &IDX);
|
||||
defmt::info!("{}: Cascade trigger timed out", &IDX);
|
||||
*IDX += 1;
|
||||
}
|
||||
|
||||
|
@@ -45,7 +45,11 @@ use va108xx_hal::{
|
||||
clock::enable_peripheral_clock,
|
||||
enable_nvic_interrupt, pac,
|
||||
prelude::*,
|
||||
timer::{enable_tim_clk, get_tim_raw, TimMarker, TimRegInterface},
|
||||
timer::{
|
||||
enable_tim_clk,
|
||||
regs::{EnableControl, MmioTimer},
|
||||
TimId, TimMarker,
|
||||
},
|
||||
PeripheralSelect,
|
||||
};
|
||||
|
||||
@@ -109,7 +113,7 @@ pub fn time_driver() -> &'static TimerDriver {
|
||||
/// This should be used if the interrupt handler is provided by the library, which is the
|
||||
/// default case.
|
||||
#[cfg(feature = "irqs-in-lib")]
|
||||
pub fn init<TimekeeperTim: TimRegInterface + TimMarker, AlarmTim: TimRegInterface + TimMarker>(
|
||||
pub fn init<TimekeeperTim: TimMarker, AlarmTim: TimMarker>(
|
||||
sysclk: Hertz,
|
||||
timekeeper_tim: TimekeeperTim,
|
||||
alarm_tim: AlarmTim,
|
||||
@@ -120,10 +124,7 @@ pub fn init<TimekeeperTim: TimRegInterface + TimMarker, AlarmTim: TimRegInterfac
|
||||
/// Initialization method for embassy when using custom IRQ handlers.
|
||||
///
|
||||
/// Requires an explicit [pac::Interrupt] argument for the timekeeper and alarm IRQs.
|
||||
pub fn init_with_custom_irqs<
|
||||
TimekeeperTim: TimRegInterface + TimMarker,
|
||||
AlarmTim: TimRegInterface + TimMarker,
|
||||
>(
|
||||
pub fn init_with_custom_irqs<TimekeeperTim: TimMarker, AlarmTim: TimMarker>(
|
||||
sysclk: Hertz,
|
||||
timekeeper_tim: TimekeeperTim,
|
||||
alarm_tim: AlarmTim,
|
||||
@@ -148,8 +149,8 @@ impl AlarmState {
|
||||
unsafe impl Send for AlarmState {}
|
||||
|
||||
static SCALE: OnceCell<u64> = OnceCell::new();
|
||||
static TIMEKEEPER_TIM: OnceCell<u8> = OnceCell::new();
|
||||
static ALARM_TIM: OnceCell<u8> = OnceCell::new();
|
||||
static TIMEKEEPER_TIM: OnceCell<TimId> = OnceCell::new();
|
||||
static ALARM_TIM: OnceCell<TimId> = OnceCell::new();
|
||||
|
||||
pub struct TimerDriver {
|
||||
periods: AtomicU32,
|
||||
@@ -160,60 +161,56 @@ pub struct TimerDriver {
|
||||
|
||||
impl TimerDriver {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn init<TimekeeperTim: TimRegInterface + TimMarker, AlarmTim: TimRegInterface + TimMarker>(
|
||||
fn init<TimekeeperTim: TimMarker, AlarmTim: TimMarker>(
|
||||
&self,
|
||||
sysclk: Hertz,
|
||||
timekeeper_tim: TimekeeperTim,
|
||||
alarm_tim: AlarmTim,
|
||||
_timekeeper_tim: TimekeeperTim,
|
||||
_alarm_tim: AlarmTim,
|
||||
timekeeper_irq: pac::Interrupt,
|
||||
alarm_irq: pac::Interrupt,
|
||||
) {
|
||||
if ALARM_TIM.get().is_some() || TIMEKEEPER_TIM.get().is_some() {
|
||||
return;
|
||||
}
|
||||
ALARM_TIM.set(AlarmTim::ID.raw_id()).ok();
|
||||
TIMEKEEPER_TIM.set(TimekeeperTim::ID.raw_id()).ok();
|
||||
ALARM_TIM.set(AlarmTim::ID).ok();
|
||||
TIMEKEEPER_TIM.set(TimekeeperTim::ID).ok();
|
||||
enable_peripheral_clock(PeripheralSelect::Irqsel);
|
||||
enable_tim_clk(timekeeper_tim.raw_id());
|
||||
let timekeeper_reg_block = timekeeper_tim.reg_block();
|
||||
let alarm_tim_reg_block = alarm_tim.reg_block();
|
||||
enable_tim_clk(TimekeeperTim::ID);
|
||||
let mut timekeeper_reg_block = unsafe { TimekeeperTim::ID.steal_regs() };
|
||||
let mut alarm_tim_reg_block = unsafe { AlarmTim::ID.steal_regs() };
|
||||
// Initiate scale value here. This is required to convert timer ticks back to a timestamp.
|
||||
SCALE.set((sysclk.raw() / TICK_HZ as u32) as u64).unwrap();
|
||||
timekeeper_reg_block
|
||||
.rst_value()
|
||||
.write(|w| unsafe { w.bits(u32::MAX) });
|
||||
timekeeper_reg_block.write_reset_value(u32::MAX);
|
||||
// Decrementing counter.
|
||||
timekeeper_reg_block
|
||||
.cnt_value()
|
||||
.write(|w| unsafe { w.bits(u32::MAX) });
|
||||
timekeeper_reg_block.write_count_value(u32::MAX);
|
||||
let irqsel = unsafe { va108xx_hal::pac::Irqsel::steal() };
|
||||
// Switch on. Timekeeping should always be done.
|
||||
irqsel
|
||||
.tim0(timekeeper_tim.raw_id() as usize)
|
||||
.tim0(TimekeeperTim::ID.value() as usize)
|
||||
.write(|w| unsafe { w.bits(timekeeper_irq as u32) });
|
||||
unsafe {
|
||||
enable_nvic_interrupt(timekeeper_irq);
|
||||
}
|
||||
timekeeper_reg_block
|
||||
.ctrl()
|
||||
.modify(|_, w| w.irq_enb().set_bit());
|
||||
timekeeper_reg_block
|
||||
.enable()
|
||||
.write(|w| unsafe { w.bits(1) });
|
||||
timekeeper_reg_block.modify_control(|mut value| {
|
||||
value.set_irq_enable(true);
|
||||
value
|
||||
});
|
||||
timekeeper_reg_block.write_enable_control(EnableControl::new_enable());
|
||||
|
||||
enable_tim_clk(alarm_tim.raw_id());
|
||||
enable_tim_clk(AlarmTim::ID);
|
||||
|
||||
// Explicitely disable alarm timer until needed.
|
||||
alarm_tim_reg_block.ctrl().modify(|_, w| {
|
||||
w.irq_enb().clear_bit();
|
||||
w.enable().clear_bit()
|
||||
alarm_tim_reg_block.modify_control(|mut value| {
|
||||
value.set_irq_enable(false);
|
||||
value.set_enable(false);
|
||||
value
|
||||
});
|
||||
// Enable general interrupts. The IRQ enable of the peripheral remains cleared.
|
||||
unsafe {
|
||||
enable_nvic_interrupt(alarm_irq);
|
||||
}
|
||||
irqsel
|
||||
.tim0(alarm_tim.raw_id() as usize)
|
||||
.tim0(AlarmTim::ID.value() as usize)
|
||||
.write(|w| unsafe { w.bits(alarm_irq as u32) });
|
||||
}
|
||||
|
||||
@@ -239,16 +236,16 @@ impl TimerDriver {
|
||||
})
|
||||
}
|
||||
|
||||
fn timekeeper_tim() -> &'static pac::tim0::RegisterBlock {
|
||||
fn timekeeper_tim() -> MmioTimer<'static> {
|
||||
TIMEKEEPER_TIM
|
||||
.get()
|
||||
.map(|idx| unsafe { get_tim_raw(*idx as usize) })
|
||||
.map(|id| unsafe { id.steal_regs() })
|
||||
.unwrap()
|
||||
}
|
||||
fn alarm_tim() -> &'static pac::tim0::RegisterBlock {
|
||||
fn alarm_tim() -> MmioTimer<'static> {
|
||||
ALARM_TIM
|
||||
.get()
|
||||
.map(|idx| unsafe { get_tim_raw(*idx as usize) })
|
||||
.map(|id| unsafe { id.steal_regs() })
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
@@ -261,25 +258,27 @@ impl TimerDriver {
|
||||
if at < t {
|
||||
self.trigger_alarm(cs);
|
||||
} else {
|
||||
let alarm_tim = Self::alarm_tim();
|
||||
let mut alarm_tim = Self::alarm_tim();
|
||||
|
||||
let remaining_ticks = (at - t).checked_mul(*SCALE.get().unwrap());
|
||||
if remaining_ticks.is_some_and(|v| v <= u32::MAX as u64) {
|
||||
alarm_tim.enable().write(|w| unsafe { w.bits(0) });
|
||||
alarm_tim
|
||||
.cnt_value()
|
||||
.write(|w| unsafe { w.bits(remaining_ticks.unwrap() as u32) });
|
||||
alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit());
|
||||
alarm_tim.enable().write(|w| unsafe { w.bits(1) });
|
||||
alarm_tim.write_enable_control(EnableControl::new_disable());
|
||||
alarm_tim.write_count_value(remaining_ticks.unwrap() as u32);
|
||||
alarm_tim.modify_control(|mut value| {
|
||||
value.set_irq_enable(true);
|
||||
value
|
||||
});
|
||||
alarm_tim.write_enable_control(EnableControl::new_enable());
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn trigger_alarm(&self, cs: CriticalSection) {
|
||||
Self::alarm_tim().ctrl().modify(|_, w| {
|
||||
w.irq_enb().clear_bit();
|
||||
w.enable().clear_bit()
|
||||
Self::alarm_tim().modify_control(|mut value| {
|
||||
value.set_irq_enable(false);
|
||||
value.set_enable(false);
|
||||
value
|
||||
});
|
||||
|
||||
let alarm = &self.alarms.borrow(cs);
|
||||
@@ -305,10 +304,11 @@ impl TimerDriver {
|
||||
if SCALE.get().is_none() {
|
||||
return false;
|
||||
}
|
||||
let alarm_tim = Self::alarm_tim();
|
||||
alarm_tim.ctrl().modify(|_, w| {
|
||||
w.irq_enb().clear_bit();
|
||||
w.enable().clear_bit()
|
||||
let mut alarm_tim = Self::alarm_tim();
|
||||
alarm_tim.modify_control(|mut value| {
|
||||
value.set_irq_enable(false);
|
||||
value.set_enable(false);
|
||||
value
|
||||
});
|
||||
|
||||
let alarm = self.alarms.borrow(cs);
|
||||
@@ -332,13 +332,14 @@ impl TimerDriver {
|
||||
// and we don't do that here.
|
||||
let safe_timestamp = timestamp.max(t + 3);
|
||||
let timer_ticks = (safe_timestamp - t).checked_mul(*SCALE.get().unwrap());
|
||||
alarm_tim.rst_value().write(|w| unsafe { w.bits(u32::MAX) });
|
||||
alarm_tim.write_reset_value(u32::MAX);
|
||||
if timer_ticks.is_some_and(|v| v <= u32::MAX as u64) {
|
||||
alarm_tim
|
||||
.cnt_value()
|
||||
.write(|w| unsafe { w.bits(timer_ticks.unwrap() as u32) });
|
||||
alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit());
|
||||
alarm_tim.enable().write(|w| unsafe { w.bits(1) });
|
||||
alarm_tim.write_count_value(timer_ticks.unwrap() as u32);
|
||||
alarm_tim.modify_control(|mut value| {
|
||||
value.set_irq_enable(true);
|
||||
value.set_enable(true);
|
||||
value
|
||||
});
|
||||
}
|
||||
// If it's too far in the future, don't enable timer yet.
|
||||
// It will be enabled later by `next_period`.
|
||||
@@ -361,7 +362,7 @@ impl Driver for TimerDriver {
|
||||
// no instructions can be reordered before the load.
|
||||
period1 = self.periods.load(Ordering::Acquire);
|
||||
|
||||
counter_val = u32::MAX - Self::timekeeper_tim().cnt_value().read().bits();
|
||||
counter_val = u32::MAX - Self::timekeeper_tim().read_count_value();
|
||||
|
||||
// Double read to protect against race conditions when the counter is overflowing.
|
||||
period2 = self.periods.load(Ordering::Relaxed);
|
||||
|
@@ -3,924 +3,4 @@
|
||||
//! ## Examples
|
||||
//!
|
||||
//! - [REB1 I2C temperature sensor example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/adt75-temp-sensor.rs)
|
||||
use crate::{clock::enable_peripheral_clock, pac, sealed::Sealed, time::Hertz, PeripheralSelect};
|
||||
use core::{marker::PhantomData, ops::Deref};
|
||||
use embedded_hal::i2c::{self, Operation, SevenBitAddress, TenBitAddress};
|
||||
|
||||
//==================================================================================================
|
||||
// Defintions
|
||||
//==================================================================================================
|
||||
|
||||
const CLK_100K: Hertz = Hertz::from_raw(100_000);
|
||||
const CLK_400K: Hertz = Hertz::from_raw(400_000);
|
||||
const MIN_CLK_400K: Hertz = Hertz::from_raw(8_000_000);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum I2cId {
|
||||
A = 0,
|
||||
B = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum FifoEmptyMode {
|
||||
Stall = 0,
|
||||
EndTransaction = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[error("clock too slow for fast I2C mode")]
|
||||
pub struct ClockTooSlowForFastI2cError;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||
#[error("invalid timing parameters")]
|
||||
pub struct InvalidTimingParamsError;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Error {
|
||||
#[error("arbitration lost")]
|
||||
ArbitrationLost,
|
||||
#[error("nack address")]
|
||||
NackAddr,
|
||||
/// Data not acknowledged in write operation
|
||||
#[error("data not acknowledged in write operation")]
|
||||
NackData,
|
||||
/// Not enough data received in read operation
|
||||
#[error("insufficient data received")]
|
||||
InsufficientDataReceived,
|
||||
/// Number of bytes in transfer too large (larger than 0x7fe)
|
||||
#[error("data too large (larger than 0x7fe)")]
|
||||
DataTooLarge,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum InitError {
|
||||
/// Wrong address used in constructor
|
||||
#[error("wrong address mode")]
|
||||
WrongAddrMode,
|
||||
/// APB1 clock is too slow for fast I2C mode.
|
||||
#[error("clock too slow for fast I2C mode: {0}")]
|
||||
ClkTooSlow(#[from] ClockTooSlowForFastI2cError),
|
||||
}
|
||||
|
||||
impl embedded_hal::i2c::Error for Error {
|
||||
fn kind(&self) -> embedded_hal::i2c::ErrorKind {
|
||||
match self {
|
||||
Error::ArbitrationLost => embedded_hal::i2c::ErrorKind::ArbitrationLoss,
|
||||
Error::NackAddr => {
|
||||
embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Address)
|
||||
}
|
||||
Error::NackData => {
|
||||
embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Data)
|
||||
}
|
||||
Error::DataTooLarge | Error::InsufficientDataReceived => {
|
||||
embedded_hal::i2c::ErrorKind::Other
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
enum I2cCmd {
|
||||
Start = 0b00,
|
||||
Stop = 0b10,
|
||||
StartWithStop = 0b11,
|
||||
Cancel = 0b100,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum I2cSpeed {
|
||||
Regular100khz = 0,
|
||||
Fast400khz = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum I2cDirection {
|
||||
Send = 0,
|
||||
Read = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum I2cAddress {
|
||||
Regular(u8),
|
||||
TenBit(u16),
|
||||
}
|
||||
|
||||
pub type I2cRegBlock = pac::i2ca::RegisterBlock;
|
||||
|
||||
/// Common trait implemented by all PAC peripheral access structures. The register block
|
||||
/// format is the same for all SPI blocks.
|
||||
pub trait I2cMarker: Deref<Target = I2cRegBlock> {
|
||||
const ID: I2cId;
|
||||
const PERIPH_SEL: PeripheralSelect;
|
||||
const PTR: *const I2cRegBlock;
|
||||
|
||||
fn ptr() -> *const I2cRegBlock;
|
||||
}
|
||||
|
||||
impl I2cMarker for pac::I2ca {
|
||||
const ID: I2cId = I2cId::A;
|
||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c0;
|
||||
const PTR: *const I2cRegBlock = Self::PTR;
|
||||
|
||||
#[inline(always)]
|
||||
fn ptr() -> *const I2cRegBlock {
|
||||
Self::ptr()
|
||||
}
|
||||
}
|
||||
|
||||
impl I2cMarker for pac::I2cb {
|
||||
const ID: I2cId = I2cId::B;
|
||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c1;
|
||||
const PTR: *const I2cRegBlock = Self::PTR;
|
||||
|
||||
#[inline(always)]
|
||||
fn ptr() -> *const I2cRegBlock {
|
||||
Self::ptr()
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Config
|
||||
//==================================================================================================
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct TrTfThighTlow(u8, u8, u8, u8);
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct TsuStoTsuStaThdStaTBuf(u8, u8, u8, u8);
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct TimingCfg {
|
||||
// 4 bit max width
|
||||
tr: u8,
|
||||
// 4 bit max width
|
||||
tf: u8,
|
||||
// 4 bit max width
|
||||
thigh: u8,
|
||||
// 4 bit max width
|
||||
tlow: u8,
|
||||
// 4 bit max width
|
||||
tsu_sto: u8,
|
||||
// 4 bit max width
|
||||
tsu_sta: u8,
|
||||
// 4 bit max width
|
||||
thd_sta: u8,
|
||||
// 4 bit max width
|
||||
tbuf: u8,
|
||||
}
|
||||
|
||||
impl TimingCfg {
|
||||
pub fn new(
|
||||
first_16_bits: TrTfThighTlow,
|
||||
second_16_bits: TsuStoTsuStaThdStaTBuf,
|
||||
) -> Result<Self, InvalidTimingParamsError> {
|
||||
if first_16_bits.0 > 0xf
|
||||
|| first_16_bits.1 > 0xf
|
||||
|| first_16_bits.2 > 0xf
|
||||
|| first_16_bits.3 > 0xf
|
||||
|| second_16_bits.0 > 0xf
|
||||
|| second_16_bits.1 > 0xf
|
||||
|| second_16_bits.2 > 0xf
|
||||
|| second_16_bits.3 > 0xf
|
||||
{
|
||||
return Err(InvalidTimingParamsError);
|
||||
}
|
||||
Ok(TimingCfg {
|
||||
tr: first_16_bits.0,
|
||||
tf: first_16_bits.1,
|
||||
thigh: first_16_bits.2,
|
||||
tlow: first_16_bits.3,
|
||||
tsu_sto: second_16_bits.0,
|
||||
tsu_sta: second_16_bits.1,
|
||||
thd_sta: second_16_bits.2,
|
||||
tbuf: second_16_bits.3,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reg(&self) -> u32 {
|
||||
((self.tbuf as u32) << 28)
|
||||
| ((self.thd_sta as u32) << 24)
|
||||
| ((self.tsu_sta as u32) << 20)
|
||||
| ((self.tsu_sto as u32) << 16)
|
||||
| ((self.tlow as u32) << 12)
|
||||
| ((self.thigh as u32) << 8)
|
||||
| ((self.tf as u32) << 4)
|
||||
| (self.tr as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TimingCfg {
|
||||
fn default() -> Self {
|
||||
TimingCfg {
|
||||
tr: 0x02,
|
||||
tf: 0x01,
|
||||
thigh: 0x08,
|
||||
tlow: 0x09,
|
||||
tsu_sto: 0x8,
|
||||
tsu_sta: 0x0a,
|
||||
thd_sta: 0x8,
|
||||
tbuf: 0xa,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct MasterConfig {
|
||||
pub tx_fe_mode: FifoEmptyMode,
|
||||
pub rx_fe_mode: FifoEmptyMode,
|
||||
/// Enable the analog delay glitch filter
|
||||
pub alg_filt: bool,
|
||||
/// Enable the digital glitch filter
|
||||
pub dlg_filt: bool,
|
||||
pub tm_cfg: Option<TimingCfg>,
|
||||
// Loopback mode
|
||||
// lbm: bool,
|
||||
}
|
||||
|
||||
impl Default for MasterConfig {
|
||||
fn default() -> Self {
|
||||
MasterConfig {
|
||||
tx_fe_mode: FifoEmptyMode::Stall,
|
||||
rx_fe_mode: FifoEmptyMode::Stall,
|
||||
alg_filt: false,
|
||||
dlg_filt: false,
|
||||
tm_cfg: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sealed for MasterConfig {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct SlaveConfig {
|
||||
pub tx_fe_mode: FifoEmptyMode,
|
||||
pub rx_fe_mode: FifoEmptyMode,
|
||||
/// Maximum number of words before issuing a negative acknowledge.
|
||||
/// Range should be 0 to 0x7fe. Setting the value to 0x7ff has the same effect as not setting
|
||||
/// the enable bit since RXCOUNT stops counting at 0x7fe.
|
||||
pub max_words: Option<usize>,
|
||||
/// A received address is compared to the ADDRESS register (addr) using the address mask
|
||||
/// (addr_mask). Those bits with a 1 in the address mask must match for there to be an address
|
||||
/// match
|
||||
pub addr: I2cAddress,
|
||||
/// The default address mask will be 0x3ff to only allow full matches
|
||||
pub addr_mask: Option<u16>,
|
||||
/// Optionally specify a second I2C address the slave interface responds to
|
||||
pub addr_b: Option<I2cAddress>,
|
||||
pub addr_b_mask: Option<u16>,
|
||||
}
|
||||
|
||||
impl SlaveConfig {
|
||||
/// Build a default slave config given a specified slave address to respond to
|
||||
pub fn new(addr: I2cAddress) -> Self {
|
||||
SlaveConfig {
|
||||
tx_fe_mode: FifoEmptyMode::Stall,
|
||||
rx_fe_mode: FifoEmptyMode::Stall,
|
||||
max_words: None,
|
||||
addr,
|
||||
addr_mask: None,
|
||||
addr_b: None,
|
||||
addr_b_mask: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sealed for SlaveConfig {}
|
||||
|
||||
//==================================================================================================
|
||||
// I2C Base
|
||||
//==================================================================================================
|
||||
|
||||
pub struct I2cCommon {
|
||||
id: I2cId,
|
||||
reg_block: *const I2cRegBlock,
|
||||
sys_clk: Hertz,
|
||||
}
|
||||
|
||||
impl I2cCommon {
|
||||
pub fn new<I2c: I2cMarker>(
|
||||
sys_clk: Hertz,
|
||||
_i2c: I2c,
|
||||
speed_mode: I2cSpeed,
|
||||
ms_cfg: Option<&MasterConfig>,
|
||||
sl_cfg: Option<&SlaveConfig>,
|
||||
) -> Result<Self, ClockTooSlowForFastI2cError> {
|
||||
enable_peripheral_clock(I2c::PERIPH_SEL);
|
||||
|
||||
let mut i2c_base = I2cCommon {
|
||||
id: I2c::ID,
|
||||
reg_block: I2c::PTR,
|
||||
sys_clk,
|
||||
};
|
||||
if let Some(ms_cfg) = ms_cfg {
|
||||
i2c_base.cfg_master(ms_cfg);
|
||||
}
|
||||
|
||||
if let Some(sl_cfg) = sl_cfg {
|
||||
i2c_base.cfg_slave(sl_cfg);
|
||||
}
|
||||
i2c_base.cfg_clk_scale(speed_mode)?;
|
||||
Ok(i2c_base)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> I2cId {
|
||||
self.id
|
||||
}
|
||||
|
||||
// Returns the address and the address mode bit.
|
||||
#[inline]
|
||||
fn unwrap_addr(addr: I2cAddress) -> (u16, u32) {
|
||||
match addr {
|
||||
I2cAddress::Regular(addr) => (addr as u16, 0 << 15),
|
||||
I2cAddress::TenBit(addr) => (addr, 1 << 15),
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve the raw register block.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Circumvents safety guarantees by the HAL.
|
||||
pub const unsafe fn regs(&self) -> &'static I2cRegBlock {
|
||||
self.regs_priv()
|
||||
}
|
||||
|
||||
const fn regs_priv(&self) -> &'static I2cRegBlock {
|
||||
unsafe { &*self.reg_block }
|
||||
}
|
||||
|
||||
fn cfg_master(&mut self, ms_cfg: &MasterConfig) {
|
||||
let (txfemd, rxfemd) = match (ms_cfg.tx_fe_mode, ms_cfg.rx_fe_mode) {
|
||||
(FifoEmptyMode::Stall, FifoEmptyMode::Stall) => (false, false),
|
||||
(FifoEmptyMode::Stall, FifoEmptyMode::EndTransaction) => (false, true),
|
||||
(FifoEmptyMode::EndTransaction, FifoEmptyMode::Stall) => (true, false),
|
||||
(FifoEmptyMode::EndTransaction, FifoEmptyMode::EndTransaction) => (true, true),
|
||||
};
|
||||
let regs = self.regs_priv();
|
||||
regs.ctrl().modify(|_, w| {
|
||||
w.txfemd().bit(txfemd);
|
||||
w.rxffmd().bit(rxfemd);
|
||||
w.dlgfilter().bit(ms_cfg.dlg_filt);
|
||||
w.algfilter().bit(ms_cfg.alg_filt)
|
||||
});
|
||||
if let Some(ref tm_cfg) = ms_cfg.tm_cfg {
|
||||
regs.tmconfig().write(|w| unsafe { w.bits(tm_cfg.reg()) });
|
||||
}
|
||||
regs.fifo_clr().write(|w| {
|
||||
w.rxfifo().set_bit();
|
||||
w.txfifo().set_bit()
|
||||
});
|
||||
}
|
||||
|
||||
fn cfg_slave(&mut self, sl_cfg: &SlaveConfig) {
|
||||
let (txfemd, rxfemd) = match (sl_cfg.tx_fe_mode, sl_cfg.rx_fe_mode) {
|
||||
(FifoEmptyMode::Stall, FifoEmptyMode::Stall) => (false, false),
|
||||
(FifoEmptyMode::Stall, FifoEmptyMode::EndTransaction) => (false, true),
|
||||
(FifoEmptyMode::EndTransaction, FifoEmptyMode::Stall) => (true, false),
|
||||
(FifoEmptyMode::EndTransaction, FifoEmptyMode::EndTransaction) => (true, true),
|
||||
};
|
||||
let regs = self.regs_priv();
|
||||
regs.s0_ctrl().modify(|_, w| {
|
||||
w.txfemd().bit(txfemd);
|
||||
w.rxffmd().bit(rxfemd)
|
||||
});
|
||||
regs.s0_fifo_clr().write(|w| {
|
||||
w.rxfifo().set_bit();
|
||||
w.txfifo().set_bit()
|
||||
});
|
||||
let max_words = sl_cfg.max_words;
|
||||
if let Some(max_words) = max_words {
|
||||
regs.s0_maxwords()
|
||||
.write(|w| unsafe { w.bits((1 << 31) | max_words as u32) });
|
||||
}
|
||||
let (addr, addr_mode_mask) = Self::unwrap_addr(sl_cfg.addr);
|
||||
// The first bit is the read/write value. Normally, both read and write are matched
|
||||
// using the RWMASK bit of the address mask register
|
||||
regs.s0_address()
|
||||
.write(|w| unsafe { w.bits((addr << 1) as u32 | addr_mode_mask) });
|
||||
if let Some(addr_mask) = sl_cfg.addr_mask {
|
||||
regs.s0_addressmask()
|
||||
.write(|w| unsafe { w.bits((addr_mask << 1) as u32) });
|
||||
}
|
||||
if let Some(addr_b) = sl_cfg.addr_b {
|
||||
let (addr, addr_mode_mask) = Self::unwrap_addr(addr_b);
|
||||
regs.s0_addressb()
|
||||
.write(|w| unsafe { w.bits((addr << 1) as u32 | addr_mode_mask) });
|
||||
}
|
||||
if let Some(addr_b_mask) = sl_cfg.addr_b_mask {
|
||||
regs.s0_addressmaskb()
|
||||
.write(|w| unsafe { w.bits((addr_b_mask << 1) as u32) });
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn filters(&mut self, digital_filt: bool, analog_filt: bool) {
|
||||
self.regs_priv().ctrl().modify(|_, w| {
|
||||
w.dlgfilter().bit(digital_filt);
|
||||
w.algfilter().bit(analog_filt)
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fifo_empty_mode(&mut self, rx: FifoEmptyMode, tx: FifoEmptyMode) {
|
||||
self.regs_priv().ctrl().modify(|_, w| {
|
||||
w.txfemd().bit(tx as u8 != 0);
|
||||
w.rxffmd().bit(rx as u8 != 0)
|
||||
});
|
||||
}
|
||||
|
||||
fn calc_clk_div(&self, speed_mode: I2cSpeed) -> Result<u8, ClockTooSlowForFastI2cError> {
|
||||
if speed_mode == I2cSpeed::Regular100khz {
|
||||
Ok(((self.sys_clk.raw() / CLK_100K.raw() / 20) - 1) as u8)
|
||||
} else {
|
||||
if self.sys_clk.raw() < MIN_CLK_400K.raw() {
|
||||
return Err(ClockTooSlowForFastI2cError);
|
||||
}
|
||||
Ok(((self.sys_clk.raw() / CLK_400K.raw() / 25) - 1) as u8)
|
||||
}
|
||||
}
|
||||
|
||||
/// Configures the clock scale for a given speed mode setting
|
||||
pub fn cfg_clk_scale(
|
||||
&mut self,
|
||||
speed_mode: I2cSpeed,
|
||||
) -> Result<(), ClockTooSlowForFastI2cError> {
|
||||
let clk_div = self.calc_clk_div(speed_mode)?;
|
||||
self.regs_priv()
|
||||
.clkscale()
|
||||
.write(|w| unsafe { w.bits(((speed_mode as u32) << 31) | clk_div as u32) });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_address(&mut self, addr: u16) {
|
||||
// Load address
|
||||
self.regs_priv()
|
||||
.address()
|
||||
.write(|w| unsafe { w.bits((addr << 1) as u32) });
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn stop_cmd(&mut self) {
|
||||
self.regs_priv()
|
||||
.cmd()
|
||||
.write(|w| unsafe { w.bits(I2cCmd::Stop as u32) });
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// I2C Master
|
||||
//==================================================================================================
|
||||
|
||||
pub struct I2cMaster<Addr = SevenBitAddress> {
|
||||
inner: I2cCommon,
|
||||
addr: PhantomData<Addr>,
|
||||
}
|
||||
|
||||
impl<Addr> I2cMaster<Addr> {
|
||||
pub fn new<I2c: I2cMarker>(
|
||||
sysclk: Hertz,
|
||||
i2c: I2c,
|
||||
cfg: MasterConfig,
|
||||
speed_mode: I2cSpeed,
|
||||
) -> Result<Self, ClockTooSlowForFastI2cError> {
|
||||
Ok(I2cMaster {
|
||||
inner: I2cCommon::new(sysclk, i2c, speed_mode, Some(&cfg), None)?,
|
||||
addr: PhantomData,
|
||||
}
|
||||
.enable_master())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cancel_transfer(&self) {
|
||||
self.inner
|
||||
.regs_priv()
|
||||
.cmd()
|
||||
.write(|w| unsafe { w.bits(I2cCmd::Cancel as u32) });
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clear_tx_fifo(&self) {
|
||||
self.inner
|
||||
.regs_priv()
|
||||
.fifo_clr()
|
||||
.write(|w| w.txfifo().set_bit());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clear_rx_fifo(&self) {
|
||||
self.inner
|
||||
.regs_priv()
|
||||
.fifo_clr()
|
||||
.write(|w| w.rxfifo().set_bit());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn enable_master(self) -> Self {
|
||||
self.inner
|
||||
.regs_priv()
|
||||
.ctrl()
|
||||
.modify(|_, w| w.enable().set_bit());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn disable_master(self) -> Self {
|
||||
self.inner
|
||||
.regs_priv()
|
||||
.ctrl()
|
||||
.modify(|_, w| w.enable().clear_bit());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn load_fifo(&self, word: u8) {
|
||||
self.inner
|
||||
.regs_priv()
|
||||
.data()
|
||||
.write(|w| unsafe { w.bits(word as u32) });
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn read_fifo(&self) -> u8 {
|
||||
self.inner.regs_priv().data().read().bits() as u8
|
||||
}
|
||||
|
||||
fn error_handler_write(&mut self, init_cmd: &I2cCmd) {
|
||||
self.clear_tx_fifo();
|
||||
if *init_cmd == I2cCmd::Start {
|
||||
self.inner.stop_cmd()
|
||||
}
|
||||
}
|
||||
|
||||
fn write_base(
|
||||
&mut self,
|
||||
addr: I2cAddress,
|
||||
init_cmd: I2cCmd,
|
||||
bytes: impl IntoIterator<Item = u8>,
|
||||
) -> Result<(), Error> {
|
||||
let mut iter = bytes.into_iter();
|
||||
// Load address
|
||||
let (addr, addr_mode_bit) = I2cCommon::unwrap_addr(addr);
|
||||
self.inner.regs_priv().address().write(|w| unsafe {
|
||||
w.bits(I2cDirection::Send as u32 | (addr << 1) as u32 | addr_mode_bit)
|
||||
});
|
||||
|
||||
self.inner
|
||||
.regs_priv()
|
||||
.cmd()
|
||||
.write(|w| unsafe { w.bits(init_cmd as u32) });
|
||||
let mut load_if_next_available = || {
|
||||
if let Some(next_byte) = iter.next() {
|
||||
self.load_fifo(next_byte);
|
||||
}
|
||||
};
|
||||
loop {
|
||||
let status_reader = self.inner.regs_priv().status().read();
|
||||
if status_reader.arblost().bit_is_set() {
|
||||
self.error_handler_write(&init_cmd);
|
||||
return Err(Error::ArbitrationLost);
|
||||
} else if status_reader.nackaddr().bit_is_set() {
|
||||
self.error_handler_write(&init_cmd);
|
||||
return Err(Error::NackAddr);
|
||||
} else if status_reader.nackdata().bit_is_set() {
|
||||
self.error_handler_write(&init_cmd);
|
||||
return Err(Error::NackData);
|
||||
} else if status_reader.idle().bit_is_set() {
|
||||
return Ok(());
|
||||
} else {
|
||||
while !status_reader.txnfull().bit_is_set() {
|
||||
load_if_next_available();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_from_buffer(
|
||||
&mut self,
|
||||
init_cmd: I2cCmd,
|
||||
addr: I2cAddress,
|
||||
output: &[u8],
|
||||
) -> Result<(), Error> {
|
||||
let len = output.len();
|
||||
// It should theoretically possible to transfer larger data sizes by tracking
|
||||
// the number of sent words and setting it to 0x7fe as soon as only that many
|
||||
// bytes are remaining. However, large transfer like this are not common. This
|
||||
// feature will therefore not be supported for now.
|
||||
if len > 0x7fe {
|
||||
return Err(Error::DataTooLarge);
|
||||
}
|
||||
// Load number of words
|
||||
self.inner
|
||||
.regs_priv()
|
||||
.words()
|
||||
.write(|w| unsafe { w.bits(len as u32) });
|
||||
let mut bytes = output.iter();
|
||||
// FIFO has a depth of 16. We load slightly above the trigger level
|
||||
// but not all of it because the transaction might fail immediately
|
||||
const FILL_DEPTH: usize = 12;
|
||||
|
||||
// load the FIFO
|
||||
for _ in 0..core::cmp::min(FILL_DEPTH, len) {
|
||||
self.load_fifo(*bytes.next().unwrap());
|
||||
}
|
||||
|
||||
self.write_base(addr, init_cmd, output.iter().cloned())
|
||||
}
|
||||
|
||||
fn read_internal(&mut self, addr: I2cAddress, buffer: &mut [u8]) -> Result<(), Error> {
|
||||
let len = buffer.len();
|
||||
// It should theoretically possible to transfer larger data sizes by tracking
|
||||
// the number of sent words and setting it to 0x7fe as soon as only that many
|
||||
// bytes are remaining. However, large transfer like this are not common. This
|
||||
// feature will therefore not be supported for now.
|
||||
if len > 0x7fe {
|
||||
return Err(Error::DataTooLarge);
|
||||
}
|
||||
// Clear the receive FIFO
|
||||
self.clear_rx_fifo();
|
||||
|
||||
// Load number of words
|
||||
self.inner
|
||||
.regs_priv()
|
||||
.words()
|
||||
.write(|w| unsafe { w.bits(len as u32) });
|
||||
let (addr, addr_mode_bit) = match addr {
|
||||
I2cAddress::Regular(addr) => (addr as u16, 0 << 15),
|
||||
I2cAddress::TenBit(addr) => (addr, 1 << 15),
|
||||
};
|
||||
// Load address
|
||||
self.inner.regs_priv().address().write(|w| unsafe {
|
||||
w.bits(I2cDirection::Read as u32 | (addr << 1) as u32 | addr_mode_bit)
|
||||
});
|
||||
|
||||
let mut buf_iter = buffer.iter_mut();
|
||||
let mut read_bytes = 0;
|
||||
// Start receive transfer
|
||||
self.inner
|
||||
.regs_priv()
|
||||
.cmd()
|
||||
.write(|w| unsafe { w.bits(I2cCmd::StartWithStop as u32) });
|
||||
let mut read_if_next_available = || {
|
||||
if let Some(next_byte) = buf_iter.next() {
|
||||
*next_byte = self.read_fifo();
|
||||
}
|
||||
};
|
||||
loop {
|
||||
let status_reader = self.inner.regs_priv().status().read();
|
||||
if status_reader.arblost().bit_is_set() {
|
||||
self.clear_rx_fifo();
|
||||
return Err(Error::ArbitrationLost);
|
||||
} else if status_reader.nackaddr().bit_is_set() {
|
||||
self.clear_rx_fifo();
|
||||
return Err(Error::NackAddr);
|
||||
} else if status_reader.idle().bit_is_set() {
|
||||
if read_bytes != len {
|
||||
return Err(Error::InsufficientDataReceived);
|
||||
}
|
||||
return Ok(());
|
||||
} else if status_reader.rxnempty().bit_is_set() {
|
||||
read_if_next_available();
|
||||
read_bytes += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//======================================================================================
|
||||
// Embedded HAL I2C implementations
|
||||
//======================================================================================
|
||||
|
||||
impl embedded_hal::i2c::ErrorType for I2cMaster<SevenBitAddress> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl embedded_hal::i2c::I2c for I2cMaster<SevenBitAddress> {
|
||||
fn transaction(
|
||||
&mut self,
|
||||
address: SevenBitAddress,
|
||||
operations: &mut [Operation<'_>],
|
||||
) -> Result<(), Self::Error> {
|
||||
for operation in operations {
|
||||
match operation {
|
||||
Operation::Read(buf) => self.read_internal(I2cAddress::Regular(address), buf)?,
|
||||
Operation::Write(buf) => self.write_from_buffer(
|
||||
I2cCmd::StartWithStop,
|
||||
I2cAddress::Regular(address),
|
||||
buf,
|
||||
)?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal::i2c::ErrorType for I2cMaster<TenBitAddress> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl embedded_hal::i2c::I2c<TenBitAddress> for I2cMaster<TenBitAddress> {
|
||||
fn transaction(
|
||||
&mut self,
|
||||
address: TenBitAddress,
|
||||
operations: &mut [Operation<'_>],
|
||||
) -> Result<(), Self::Error> {
|
||||
for operation in operations {
|
||||
match operation {
|
||||
Operation::Read(buf) => self.read_internal(I2cAddress::TenBit(address), buf)?,
|
||||
Operation::Write(buf) => {
|
||||
self.write_from_buffer(I2cCmd::StartWithStop, I2cAddress::TenBit(address), buf)?
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// I2C Slave
|
||||
//==================================================================================================
|
||||
|
||||
pub struct I2cSlave<Addr = SevenBitAddress> {
|
||||
inner: I2cCommon,
|
||||
addr: PhantomData<Addr>,
|
||||
}
|
||||
|
||||
impl<Addr> I2cSlave<Addr> {
|
||||
fn new_generic<I2c: I2cMarker>(
|
||||
sys_clk: Hertz,
|
||||
i2c: I2c,
|
||||
cfg: SlaveConfig,
|
||||
speed_mode: I2cSpeed,
|
||||
) -> Result<Self, ClockTooSlowForFastI2cError> {
|
||||
Ok(I2cSlave {
|
||||
inner: I2cCommon::new(sys_clk, i2c, speed_mode, None, Some(&cfg))?,
|
||||
addr: PhantomData,
|
||||
}
|
||||
.enable_slave())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn enable_slave(self) -> Self {
|
||||
self.inner
|
||||
.regs_priv()
|
||||
.s0_ctrl()
|
||||
.modify(|_, w| w.enable().set_bit());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn disable_slave(self) -> Self {
|
||||
self.inner
|
||||
.regs_priv()
|
||||
.s0_ctrl()
|
||||
.modify(|_, w| w.enable().clear_bit());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn load_fifo(&self, word: u8) {
|
||||
self.inner
|
||||
.regs_priv()
|
||||
.s0_data()
|
||||
.write(|w| unsafe { w.bits(word as u32) });
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn read_fifo(&self) -> u8 {
|
||||
self.inner.regs_priv().s0_data().read().bits() as u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clear_tx_fifo(&self) {
|
||||
self.inner
|
||||
.regs_priv()
|
||||
.s0_fifo_clr()
|
||||
.write(|w| w.txfifo().set_bit());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clear_rx_fifo(&self) {
|
||||
self.inner
|
||||
.regs_priv()
|
||||
.s0_fifo_clr()
|
||||
.write(|w| w.rxfifo().set_bit());
|
||||
}
|
||||
|
||||
/// Get the last address that was matched by the slave control and the corresponding
|
||||
/// master direction
|
||||
pub fn last_address(&self) -> (I2cDirection, u32) {
|
||||
let bits = self.inner.regs_priv().s0_lastaddress().read().bits();
|
||||
match bits & 0x01 {
|
||||
0 => (I2cDirection::Send, bits >> 1),
|
||||
1 => (I2cDirection::Read, bits >> 1),
|
||||
_ => (I2cDirection::Send, bits >> 1),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, output: &[u8]) -> Result<(), Error> {
|
||||
let len = output.len();
|
||||
// It should theoretically possible to transfer larger data sizes by tracking
|
||||
// the number of sent words and setting it to 0x7fe as soon as only that many
|
||||
// bytes are remaining. However, large transfer like this are not common. This
|
||||
// feature will therefore not be supported for now.
|
||||
if len > 0x7fe {
|
||||
return Err(Error::DataTooLarge);
|
||||
}
|
||||
let mut bytes = output.iter();
|
||||
// FIFO has a depth of 16. We load slightly above the trigger level
|
||||
// but not all of it because the transaction might fail immediately
|
||||
const FILL_DEPTH: usize = 12;
|
||||
|
||||
// load the FIFO
|
||||
for _ in 0..core::cmp::min(FILL_DEPTH, len) {
|
||||
self.load_fifo(*bytes.next().unwrap());
|
||||
}
|
||||
|
||||
let status_reader = self.inner.regs_priv().s0_status().read();
|
||||
let mut load_if_next_available = || {
|
||||
if let Some(next_byte) = bytes.next() {
|
||||
self.load_fifo(*next_byte);
|
||||
}
|
||||
};
|
||||
loop {
|
||||
if status_reader.nackdata().bit_is_set() {
|
||||
self.clear_tx_fifo();
|
||||
return Err(Error::NackData);
|
||||
} else if status_reader.idle().bit_is_set() {
|
||||
return Ok(());
|
||||
} else {
|
||||
while !status_reader.txnfull().bit_is_set() {
|
||||
load_if_next_available();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
|
||||
let len = buffer.len();
|
||||
// It should theoretically possible to transfer larger data sizes by tracking
|
||||
// the number of sent words and setting it to 0x7fe as soon as only that many
|
||||
// bytes are remaining. However, large transfer like this are not common. This
|
||||
// feature will therefore not be supported for now.
|
||||
if len > 0x7fe {
|
||||
return Err(Error::DataTooLarge);
|
||||
}
|
||||
// Clear the receive FIFO
|
||||
self.clear_rx_fifo();
|
||||
|
||||
let mut buf_iter = buffer.iter_mut();
|
||||
let mut read_bytes = 0;
|
||||
let mut read_if_next_available = || {
|
||||
if let Some(next_byte) = buf_iter.next() {
|
||||
*next_byte = self.read_fifo();
|
||||
}
|
||||
};
|
||||
loop {
|
||||
let status_reader = self.inner.regs_priv().s0_status().read();
|
||||
if status_reader.idle().bit_is_set() {
|
||||
if read_bytes != len {
|
||||
return Err(Error::InsufficientDataReceived);
|
||||
}
|
||||
return Ok(());
|
||||
} else if status_reader.rxnempty().bit_is_set() {
|
||||
read_bytes += 1;
|
||||
read_if_next_available();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl I2cSlave<SevenBitAddress> {
|
||||
/// Create a new I2C slave for seven bit addresses
|
||||
pub fn new<I2c: I2cMarker>(
|
||||
sys_clk: Hertz,
|
||||
i2c: I2c,
|
||||
cfg: SlaveConfig,
|
||||
speed_mode: I2cSpeed,
|
||||
) -> Result<Self, InitError> {
|
||||
if let I2cAddress::TenBit(_) = cfg.addr {
|
||||
return Err(InitError::WrongAddrMode);
|
||||
}
|
||||
Ok(Self::new_generic(sys_clk, i2c, cfg, speed_mode)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl I2cSlave<TenBitAddress> {
|
||||
pub fn new_ten_bit_addr<I2c: I2cMarker>(
|
||||
sys_clk: Hertz,
|
||||
i2c: I2c,
|
||||
cfg: SlaveConfig,
|
||||
speed_mode: I2cSpeed,
|
||||
) -> Result<Self, ClockTooSlowForFastI2cError> {
|
||||
Self::new_generic(sys_clk, i2c, cfg, speed_mode)
|
||||
}
|
||||
}
|
||||
pub use vorago_shared_periphs::i2c::*;
|
||||
|
@@ -5,272 +5,4 @@
|
||||
//! ## Examples
|
||||
//!
|
||||
//! - [PWM example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/pwm.rs)
|
||||
use core::convert::Infallible;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use vorago_shared_periphs::gpio::IoPeriphPin;
|
||||
use vorago_shared_periphs::PeripheralSelect;
|
||||
|
||||
use crate::clock::enable_peripheral_clock;
|
||||
use crate::time::Hertz;
|
||||
use crate::timer::{TimId, TimMarker, TimPin, TimRegInterface};
|
||||
|
||||
const DUTY_MAX: u16 = u16::MAX;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
enum StatusSelPwm {
|
||||
PwmA = 3,
|
||||
PwmB = 4,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct PwmA {}
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct PwmB {}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[error("pin tim ID {pin_tim:?} and timer tim id {tim_id:?} do not match")]
|
||||
pub struct TimMissmatchError {
|
||||
pin_tim: TimId,
|
||||
tim_id: TimId,
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// PWM pin
|
||||
//==================================================================================================
|
||||
|
||||
/// Reduced version where type information is deleted
|
||||
pub struct PwmPin<Mode = PwmA> {
|
||||
tim_id: TimId,
|
||||
sys_clk: Hertz,
|
||||
/// For PWMB, this is the upper limit
|
||||
current_duty: u16,
|
||||
/// For PWMA, this value will not be used
|
||||
current_lower_limit: u16,
|
||||
current_period: Hertz,
|
||||
current_rst_val: u32,
|
||||
mode: PhantomData<Mode>,
|
||||
}
|
||||
|
||||
impl<Mode> PwmPin<Mode> {
|
||||
/// Create a new strongly typed PWM pin
|
||||
pub fn new<Pin: TimPin, Tim: TimMarker + TimRegInterface>(
|
||||
sys_clk: Hertz,
|
||||
pin_and_tim: (Pin, Tim),
|
||||
initial_frequency: Hertz,
|
||||
) -> Result<Self, TimMissmatchError> {
|
||||
if Pin::TIM_ID != Tim::ID {
|
||||
return Err(TimMissmatchError {
|
||||
pin_tim: Pin::TIM_ID,
|
||||
tim_id: Tim::ID,
|
||||
});
|
||||
}
|
||||
IoPeriphPin::new(Pin::PIN_ID, Pin::FUN_SEL, None);
|
||||
let mut pin = PwmPin {
|
||||
tim_id: Tim::ID,
|
||||
current_duty: 0,
|
||||
current_lower_limit: 0,
|
||||
current_period: initial_frequency,
|
||||
current_rst_val: 0,
|
||||
sys_clk,
|
||||
mode: PhantomData,
|
||||
};
|
||||
enable_peripheral_clock(PeripheralSelect::Gpio);
|
||||
enable_peripheral_clock(PeripheralSelect::Ioconfig);
|
||||
let syscfg = unsafe { va108xx::Sysconfig::steal() };
|
||||
syscfg
|
||||
.tim_clk_enable()
|
||||
.modify(|r, w| unsafe { w.bits(r.bits() | pin_and_tim.1.mask_32()) });
|
||||
pin.enable_pwm_a();
|
||||
pin.set_period(initial_frequency);
|
||||
Ok(pin)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn enable_pwm_a(&mut self) {
|
||||
self.tim_id
|
||||
.reg_block()
|
||||
.ctrl()
|
||||
.modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmA as u8) });
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn enable_pwm_b(&mut self) {
|
||||
self.tim_id
|
||||
.reg_block()
|
||||
.ctrl()
|
||||
.modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmB as u8) });
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_period(&self) -> Hertz {
|
||||
self.current_period
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_period(&mut self, period: impl Into<Hertz>) {
|
||||
self.current_period = period.into();
|
||||
// Avoid division by 0
|
||||
if self.current_period.raw() == 0 {
|
||||
return;
|
||||
}
|
||||
self.current_rst_val = self.sys_clk.raw() / self.current_period.raw();
|
||||
self.tim_id
|
||||
.reg_block()
|
||||
.rst_value()
|
||||
.write(|w| unsafe { w.bits(self.current_rst_val) });
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn disable(&mut self) {
|
||||
self.tim_id
|
||||
.reg_block()
|
||||
.ctrl()
|
||||
.modify(|_, w| w.enable().clear_bit());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn enable(&mut self) {
|
||||
self.tim_id
|
||||
.reg_block()
|
||||
.ctrl()
|
||||
.modify(|_, w| w.enable().set_bit());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn period(&self) -> Hertz {
|
||||
self.current_period
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn duty(&self) -> u16 {
|
||||
self.current_duty
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PwmPin<PwmA>> for PwmPin<PwmB> {
|
||||
fn from(other: PwmPin<PwmA>) -> Self {
|
||||
let mut pwmb = Self {
|
||||
mode: PhantomData,
|
||||
tim_id: other.tim_id,
|
||||
sys_clk: other.sys_clk,
|
||||
current_duty: other.current_duty,
|
||||
current_lower_limit: other.current_lower_limit,
|
||||
current_period: other.current_period,
|
||||
current_rst_val: other.current_rst_val,
|
||||
};
|
||||
pwmb.enable_pwm_b();
|
||||
pwmb
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PwmPin<PwmB>> for PwmPin<PwmA> {
|
||||
fn from(other: PwmPin<PwmB>) -> Self {
|
||||
let mut pwmb = Self {
|
||||
mode: PhantomData,
|
||||
tim_id: other.tim_id,
|
||||
sys_clk: other.sys_clk,
|
||||
current_duty: other.current_duty,
|
||||
current_lower_limit: other.current_lower_limit,
|
||||
current_period: other.current_period,
|
||||
current_rst_val: other.current_rst_val,
|
||||
};
|
||||
pwmb.enable_pwm_a();
|
||||
pwmb
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// PWMB implementations
|
||||
//==================================================================================================
|
||||
|
||||
impl PwmPin<PwmB> {
|
||||
#[inline(always)]
|
||||
pub fn pwmb_lower_limit(&self) -> u16 {
|
||||
self.current_lower_limit
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn pwmb_upper_limit(&self) -> u16 {
|
||||
self.current_duty
|
||||
}
|
||||
|
||||
/// Set the lower limit for PWMB
|
||||
///
|
||||
/// The PWM signal will be 1 as long as the current RST counter is larger than
|
||||
/// the lower limit. For example, with a lower limit of 0.5 and and an upper limit
|
||||
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
|
||||
/// state
|
||||
#[inline(always)]
|
||||
pub fn set_pwmb_lower_limit(&mut self, duty: u16) {
|
||||
self.current_lower_limit = duty;
|
||||
let pwmb_val: u64 =
|
||||
(self.current_rst_val as u64 * self.current_lower_limit as u64) / DUTY_MAX as u64;
|
||||
self.tim_id
|
||||
.reg_block()
|
||||
.pwmb_value()
|
||||
.write(|w| unsafe { w.bits(pwmb_val as u32) });
|
||||
}
|
||||
|
||||
/// Set the higher limit for PWMB
|
||||
///
|
||||
/// The PWM signal will be 1 as long as the current RST counter is smaller than
|
||||
/// the higher limit. For example, with a lower limit of 0.5 and and an upper limit
|
||||
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
|
||||
/// state
|
||||
pub fn set_pwmb_upper_limit(&mut self, duty: u16) {
|
||||
self.current_duty = duty;
|
||||
let pwma_val: u64 =
|
||||
(self.current_rst_val as u64 * self.current_duty as u64) / DUTY_MAX as u64;
|
||||
self.tim_id
|
||||
.reg_block()
|
||||
.pwma_value()
|
||||
.write(|w| unsafe { w.bits(pwma_val as u32) });
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Embedded HAL implementation: PWMA only
|
||||
//==================================================================================================
|
||||
|
||||
impl embedded_hal::pwm::ErrorType for PwmPin {
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
impl embedded_hal::pwm::SetDutyCycle for PwmPin {
|
||||
#[inline]
|
||||
fn max_duty_cycle(&self) -> u16 {
|
||||
DUTY_MAX
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
|
||||
self.current_duty = duty;
|
||||
let pwma_val: u64 = (self.current_rst_val as u64
|
||||
* (DUTY_MAX as u64 - self.current_duty as u64))
|
||||
/ DUTY_MAX as u64;
|
||||
self.tim_id
|
||||
.reg_block()
|
||||
.pwma_value()
|
||||
.write(|w| unsafe { w.bits(pwma_val as u32) });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the corresponding u16 duty cycle from a percent value ranging between 0.0 and 1.0.
|
||||
///
|
||||
/// Please note that this might load a lot of floating point code because this processor does not
|
||||
/// have a FPU
|
||||
pub fn get_duty_from_percent(percent: f32) -> u16 {
|
||||
if percent > 1.0 {
|
||||
DUTY_MAX
|
||||
} else if percent <= 0.0 {
|
||||
0
|
||||
} else {
|
||||
(percent * DUTY_MAX as f32) as u16
|
||||
}
|
||||
}
|
||||
pub use vorago_shared_periphs::pwm::*;
|
||||
|
@@ -9,902 +9,6 @@
|
||||
//! - [Blocking SPI example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/spi.rs)
|
||||
//! - [REB1 ADC example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/examples/max11519-adc.rs)
|
||||
//! - [REB1 EEPROM library](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/vorago-reb1/src/m95m01.rs)
|
||||
use crate::{
|
||||
clock::enable_peripheral_clock, pac, pins::PinMarker, sealed::Sealed, time::Hertz,
|
||||
PeripheralSelect,
|
||||
};
|
||||
use core::{convert::Infallible, fmt::Debug, marker::PhantomData, ops::Deref};
|
||||
use embedded_hal::spi::{Mode, MODE_0};
|
||||
use pins::{HwCsProvider, PinMiso, PinMosi, PinSck};
|
||||
use vorago_shared_periphs::gpio::IoPeriphPin;
|
||||
pub use vorago_shared_periphs::spi::*;
|
||||
|
||||
pub mod pins;
|
||||
|
||||
pub fn configure_pin_as_hw_cs_pin<P: PinMarker + HwCsProvider>(_pin: P) -> HwChipSelectId {
|
||||
IoPeriphPin::new(P::ID, P::FUN_SEL, None);
|
||||
P::CS_ID
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Defintions
|
||||
//==================================================================================================
|
||||
|
||||
// FIFO has a depth of 16.
|
||||
const FILL_DEPTH: usize = 12;
|
||||
|
||||
pub const BMSTART_BMSTOP_MASK: u32 = 1 << 31;
|
||||
pub const BMSKIPDATA_MASK: u32 = 1 << 30;
|
||||
|
||||
pub const DEFAULT_CLK_DIV: u16 = 2;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum HwChipSelectId {
|
||||
Id0 = 0,
|
||||
Id1 = 1,
|
||||
Id2 = 2,
|
||||
Id3 = 3,
|
||||
Id4 = 4,
|
||||
Id5 = 5,
|
||||
Id6 = 6,
|
||||
Id7 = 7,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum SpiId {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
}
|
||||
|
||||
impl SpiId {
|
||||
/// Unsafely steal a peripheral MMIO block for the given UART.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Circumvents ownership and safety guarantees by the HAL which can lead to data races
|
||||
/// on cuncurrent usage.
|
||||
pub unsafe fn reg_block(&self) -> &'static SpiRegBlock {
|
||||
unsafe {
|
||||
match self {
|
||||
SpiId::A => va108xx::Spia::steal().reg_block(),
|
||||
SpiId::B => va108xx::Spib::steal().reg_block(),
|
||||
SpiId::C => va108xx::Spic::steal().reg_block(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum WordSize {
|
||||
OneBit = 0x00,
|
||||
FourBits = 0x03,
|
||||
EightBits = 0x07,
|
||||
SixteenBits = 0x0f,
|
||||
}
|
||||
|
||||
pub type SpiRegBlock = pac::spia::RegisterBlock;
|
||||
|
||||
/// Common trait implemented by all PAC peripheral access structures. The register block
|
||||
/// format is the same for all SPI blocks.
|
||||
pub trait SpiMarker: Deref<Target = SpiRegBlock> + Sealed {
|
||||
const ID: SpiId;
|
||||
const PERIPH_SEL: PeripheralSelect;
|
||||
const PTR: *const SpiRegBlock;
|
||||
|
||||
fn ptr() -> *const SpiRegBlock;
|
||||
|
||||
#[inline(always)]
|
||||
fn reg_block(&self) -> &'static mut SpiRegBlock {
|
||||
unsafe { &mut *(Self::ptr() as *mut _) }
|
||||
}
|
||||
}
|
||||
|
||||
impl SpiMarker for pac::Spia {
|
||||
const ID: SpiId = SpiId::A;
|
||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi0;
|
||||
const PTR: *const SpiRegBlock = Self::PTR;
|
||||
|
||||
#[inline(always)]
|
||||
fn ptr() -> *const SpiRegBlock {
|
||||
Self::ptr()
|
||||
}
|
||||
}
|
||||
impl Sealed for pac::Spia {}
|
||||
|
||||
impl SpiMarker for pac::Spib {
|
||||
const ID: SpiId = SpiId::B;
|
||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi1;
|
||||
const PTR: *const SpiRegBlock = Self::PTR;
|
||||
|
||||
#[inline(always)]
|
||||
fn ptr() -> *const SpiRegBlock {
|
||||
Self::ptr()
|
||||
}
|
||||
}
|
||||
impl Sealed for pac::Spib {}
|
||||
|
||||
impl SpiMarker for pac::Spic {
|
||||
const ID: SpiId = SpiId::C;
|
||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi2;
|
||||
const PTR: *const SpiRegBlock = Self::PTR;
|
||||
|
||||
#[inline(always)]
|
||||
fn ptr() -> *const SpiRegBlock {
|
||||
Self::ptr()
|
||||
}
|
||||
}
|
||||
impl Sealed for pac::Spic {}
|
||||
|
||||
//==================================================================================================
|
||||
// Config
|
||||
//==================================================================================================
|
||||
|
||||
pub trait TransferConfigProvider {
|
||||
fn sod(&mut self, sod: bool);
|
||||
fn blockmode(&mut self, blockmode: bool);
|
||||
fn mode(&mut self, mode: Mode);
|
||||
fn clk_cfg(&mut self, clk_cfg: SpiClkConfig);
|
||||
fn hw_cs_id(&self) -> u8;
|
||||
}
|
||||
|
||||
/// Type erased variant of the transfer configuration. This is required to avoid generics in
|
||||
/// the SPI constructor.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct TransferConfig {
|
||||
pub clk_cfg: Option<SpiClkConfig>,
|
||||
pub mode: Option<Mode>,
|
||||
pub sod: bool,
|
||||
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
|
||||
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
|
||||
/// duration of multiple data words
|
||||
pub blockmode: bool,
|
||||
/// Only used when blockmode is used. The SCK will be stalled until an explicit stop bit
|
||||
/// is set on a written word.
|
||||
pub bmstall: bool,
|
||||
pub hw_cs: Option<HwChipSelectId>,
|
||||
}
|
||||
|
||||
impl TransferConfig {
|
||||
pub fn new_with_hw_cs(
|
||||
clk_cfg: Option<SpiClkConfig>,
|
||||
mode: Option<Mode>,
|
||||
blockmode: bool,
|
||||
bmstall: bool,
|
||||
sod: bool,
|
||||
hw_cs_id: HwChipSelectId,
|
||||
) -> Self {
|
||||
TransferConfig {
|
||||
clk_cfg,
|
||||
mode,
|
||||
sod,
|
||||
blockmode,
|
||||
bmstall,
|
||||
hw_cs: Some(hw_cs_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct SpiConfig {
|
||||
clk: SpiClkConfig,
|
||||
// SPI mode configuration
|
||||
pub init_mode: Mode,
|
||||
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
|
||||
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
|
||||
/// duration of multiple data words. Defaults to true.
|
||||
pub blockmode: bool,
|
||||
/// This enables the stalling of the SPI SCK if in blockmode and the FIFO is empty.
|
||||
/// Currently enabled by default.
|
||||
pub bmstall: bool,
|
||||
/// By default, configure SPI for master mode (ms == false)
|
||||
ms: bool,
|
||||
/// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control
|
||||
pub slave_output_disable: bool,
|
||||
/// Loopback mode. If you use this, don't connect MISO to MOSI, they will be tied internally
|
||||
pub loopback_mode: bool,
|
||||
/// Enable Master Delayer Capture Mode. See Programmers Guide p.92 for more details
|
||||
pub master_delayer_capture: bool,
|
||||
}
|
||||
|
||||
impl Default for SpiConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
init_mode: MODE_0,
|
||||
blockmode: true,
|
||||
bmstall: true,
|
||||
// Default value is definitely valid.
|
||||
clk: SpiClkConfig::from_div(DEFAULT_CLK_DIV).unwrap(),
|
||||
ms: Default::default(),
|
||||
slave_output_disable: Default::default(),
|
||||
loopback_mode: Default::default(),
|
||||
master_delayer_capture: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpiConfig {
|
||||
pub fn loopback(mut self, enable: bool) -> Self {
|
||||
self.loopback_mode = enable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn blockmode(mut self, enable: bool) -> Self {
|
||||
self.blockmode = enable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bmstall(mut self, enable: bool) -> Self {
|
||||
self.bmstall = enable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mode(mut self, mode: Mode) -> Self {
|
||||
self.init_mode = mode;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn clk_cfg(mut self, clk_cfg: SpiClkConfig) -> Self {
|
||||
self.clk = clk_cfg;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn master_mode(mut self, master: bool) -> Self {
|
||||
self.ms = !master;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn slave_output_disable(mut self, sod: bool) -> Self {
|
||||
self.slave_output_disable = sod;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Word Size
|
||||
//==================================================================================================
|
||||
|
||||
/// Configuration trait for the Word Size
|
||||
/// used by the SPI peripheral
|
||||
pub trait WordProvider: Copy + Default + Into<u32> + TryFrom<u32> + 'static {
|
||||
const MASK: u32;
|
||||
fn word_reg() -> u8;
|
||||
}
|
||||
|
||||
impl WordProvider for u8 {
|
||||
const MASK: u32 = 0xff;
|
||||
fn word_reg() -> u8 {
|
||||
0x07
|
||||
}
|
||||
}
|
||||
|
||||
impl WordProvider for u16 {
|
||||
const MASK: u32 = 0xffff;
|
||||
fn word_reg() -> u8 {
|
||||
0x0f
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Spi
|
||||
//==================================================================================================
|
||||
|
||||
/// Low level access trait for the SPI peripheral.
|
||||
pub trait SpiLowLevel {
|
||||
/// Low level function to write a word to the SPI FIFO but also checks whether
|
||||
/// there is actually data in the FIFO.
|
||||
///
|
||||
/// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
|
||||
fn write_fifo(&mut self, data: u32) -> nb::Result<(), Infallible>;
|
||||
|
||||
/// Low level function to write a word to the SPI FIFO without checking whether
|
||||
/// there FIFO is full.
|
||||
///
|
||||
/// This does not necesarily mean there is a space in the FIFO available.
|
||||
/// Use [Self::write_fifo] function to write a word into the FIFO reliably.
|
||||
fn write_fifo_unchecked(&mut self, data: u32);
|
||||
|
||||
/// Low level function to read a word from the SPI FIFO. Must be preceeded by a
|
||||
/// [Self::write_fifo] call.
|
||||
///
|
||||
/// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
|
||||
fn read_fifo(&mut self) -> nb::Result<u32, Infallible>;
|
||||
|
||||
/// Low level function to read a word from from the SPI FIFO.
|
||||
///
|
||||
/// This does not necesarily mean there is a word in the FIFO available.
|
||||
/// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb]
|
||||
/// API.
|
||||
/// You might also need to mask the value to ignore the BMSTART/BMSTOP bit.
|
||||
fn read_fifo_unchecked(&mut self) -> u32;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) {
|
||||
match mode {
|
||||
embedded_hal::spi::MODE_0 => (false, false),
|
||||
embedded_hal::spi::MODE_1 => (false, true),
|
||||
embedded_hal::spi::MODE_2 => (true, false),
|
||||
embedded_hal::spi::MODE_3 => (true, true),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct SpiClkConfig {
|
||||
prescale_val: u16,
|
||||
scrdv: u8,
|
||||
}
|
||||
|
||||
impl SpiClkConfig {
|
||||
pub fn prescale_val(&self) -> u16 {
|
||||
self.prescale_val
|
||||
}
|
||||
pub fn scrdv(&self) -> u8 {
|
||||
self.scrdv
|
||||
}
|
||||
}
|
||||
|
||||
impl SpiClkConfig {
|
||||
pub fn new(prescale_val: u16, scrdv: u8) -> Self {
|
||||
Self {
|
||||
prescale_val,
|
||||
scrdv,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_div(div: u16) -> Result<Self, SpiClkConfigError> {
|
||||
spi_clk_config_from_div(div)
|
||||
}
|
||||
|
||||
pub fn from_clk(sys_clk: impl Into<Hertz>, spi_clk: impl Into<Hertz>) -> Option<Self> {
|
||||
clk_div_for_target_clock(sys_clk, spi_clk).map(|div| spi_clk_config_from_div(div).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum SpiClkConfigError {
|
||||
#[error("division by zero")]
|
||||
DivIsZero,
|
||||
#[error("divide value is not even")]
|
||||
DivideValueNotEven,
|
||||
#[error("scrdv value is too large")]
|
||||
ScrdvValueTooLarge,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn spi_clk_config_from_div(mut div: u16) -> Result<SpiClkConfig, SpiClkConfigError> {
|
||||
if div == 0 {
|
||||
return Err(SpiClkConfigError::DivIsZero);
|
||||
}
|
||||
if div % 2 != 0 {
|
||||
return Err(SpiClkConfigError::DivideValueNotEven);
|
||||
}
|
||||
let mut prescale_val = 0;
|
||||
|
||||
// find largest (even) prescale value that divides into div
|
||||
for i in (2..=0xfe).rev().step_by(2) {
|
||||
if div % i == 0 {
|
||||
prescale_val = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if prescale_val == 0 {
|
||||
return Err(SpiClkConfigError::DivideValueNotEven);
|
||||
}
|
||||
|
||||
div /= prescale_val;
|
||||
if div > u8::MAX as u16 + 1 {
|
||||
return Err(SpiClkConfigError::ScrdvValueTooLarge);
|
||||
}
|
||||
Ok(SpiClkConfig {
|
||||
prescale_val,
|
||||
scrdv: (div - 1) as u8,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clk_div_for_target_clock(
|
||||
sys_clk: impl Into<Hertz>,
|
||||
spi_clk: impl Into<Hertz>,
|
||||
) -> Option<u16> {
|
||||
let spi_clk = spi_clk.into();
|
||||
let sys_clk = sys_clk.into();
|
||||
if spi_clk > sys_clk {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Step 1: Calculate raw divider.
|
||||
let raw_div = sys_clk.raw() / spi_clk.raw();
|
||||
let remainder = sys_clk.raw() % spi_clk.raw();
|
||||
|
||||
// Step 2: Round up if necessary.
|
||||
let mut rounded_div = if remainder * 2 >= spi_clk.raw() {
|
||||
raw_div + 1
|
||||
} else {
|
||||
raw_div
|
||||
};
|
||||
|
||||
if rounded_div % 2 != 0 {
|
||||
// Take slower clock conservatively.
|
||||
rounded_div += 1;
|
||||
}
|
||||
if rounded_div > u16::MAX as u32 {
|
||||
return None;
|
||||
}
|
||||
Some(rounded_div as u16)
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("peripheral or peripheral pin ID is not consistent")]
|
||||
pub struct SpiIdMissmatchError;
|
||||
|
||||
/// SPI peripheral driver structure.
|
||||
pub struct Spi<Word = u8> {
|
||||
id: SpiId,
|
||||
reg_block: *const SpiRegBlock,
|
||||
cfg: SpiConfig,
|
||||
sys_clk: Hertz,
|
||||
/// Fill word for read-only SPI transactions.
|
||||
fill_word: Word,
|
||||
blockmode: bool,
|
||||
bmstall: bool,
|
||||
word: PhantomData<Word>,
|
||||
}
|
||||
|
||||
impl<Word: WordProvider> Spi<Word>
|
||||
where
|
||||
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
||||
{
|
||||
/// Create a new SPI struct.
|
||||
///
|
||||
/// ## Arguments
|
||||
/// * `sys_clk` - System clock
|
||||
/// * `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
|
||||
pub fn new_for_rom<SpiI: SpiMarker>(
|
||||
sys_clk: Hertz,
|
||||
spi: SpiI,
|
||||
spi_cfg: SpiConfig,
|
||||
) -> Result<Self, SpiIdMissmatchError> {
|
||||
if SpiI::ID != SpiId::C {
|
||||
return Err(SpiIdMissmatchError);
|
||||
}
|
||||
Ok(Self::new_generic(sys_clk, spi, spi_cfg))
|
||||
}
|
||||
/// Create a new SPI struct.
|
||||
///
|
||||
/// ## Arguments
|
||||
/// * `sys_clk` - System clock
|
||||
/// * `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
|
||||
pub fn new<SpiI: SpiMarker, Sck: PinSck, Miso: PinMiso, Mosi: PinMosi>(
|
||||
sys_clk: Hertz,
|
||||
spi: SpiI,
|
||||
_pins: (Sck, Miso, Mosi),
|
||||
spi_cfg: SpiConfig,
|
||||
) -> Result<Self, SpiIdMissmatchError> {
|
||||
if SpiI::ID != Sck::SPI_ID || SpiI::ID != Miso::SPI_ID || SpiI::ID != Mosi::SPI_ID {
|
||||
return Err(SpiIdMissmatchError);
|
||||
}
|
||||
IoPeriphPin::new(Sck::ID, Sck::FUN_SEL, None);
|
||||
IoPeriphPin::new(Miso::ID, Miso::FUN_SEL, None);
|
||||
IoPeriphPin::new(Mosi::ID, Mosi::FUN_SEL, None);
|
||||
Ok(Self::new_generic(sys_clk, spi, spi_cfg))
|
||||
}
|
||||
|
||||
pub fn new_generic<SpiI: SpiMarker>(sys_clk: Hertz, spi: SpiI, spi_cfg: SpiConfig) -> Self {
|
||||
enable_peripheral_clock(SpiI::PERIPH_SEL);
|
||||
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(spi_cfg.init_mode);
|
||||
spi.ctrl0().write(|w| {
|
||||
unsafe {
|
||||
w.size().bits(Word::word_reg());
|
||||
w.scrdv().bits(spi_cfg.clk.scrdv);
|
||||
// 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(spi_cfg.loopback_mode);
|
||||
w.sod().bit(spi_cfg.slave_output_disable);
|
||||
w.ms().bit(spi_cfg.ms);
|
||||
w.mdlycap().bit(spi_cfg.master_delayer_capture);
|
||||
w.blockmode().bit(spi_cfg.blockmode);
|
||||
w.bmstall().bit(spi_cfg.bmstall);
|
||||
unsafe { w.ss().bits(0) }
|
||||
});
|
||||
spi.clkprescale()
|
||||
.write(|w| unsafe { w.bits(spi_cfg.clk.prescale_val as u32) });
|
||||
|
||||
spi.fifo_clr().write(|w| {
|
||||
w.rxfifo().set_bit();
|
||||
w.txfifo().set_bit()
|
||||
});
|
||||
// Enable the peripheral as the last step as recommended in the
|
||||
// programmers guide
|
||||
spi.ctrl1().modify(|_, w| w.enable().set_bit());
|
||||
Spi {
|
||||
id: SpiI::ID,
|
||||
reg_block: SpiI::PTR,
|
||||
cfg: spi_cfg,
|
||||
sys_clk,
|
||||
fill_word: Default::default(),
|
||||
bmstall: spi_cfg.bmstall,
|
||||
blockmode: spi_cfg.blockmode,
|
||||
word: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn reg_block(&self) -> &'static SpiRegBlock {
|
||||
unsafe { &*(self.reg_block) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cfg_clock(&mut self, cfg: SpiClkConfig) {
|
||||
self.reg_block()
|
||||
.ctrl0()
|
||||
.modify(|_, w| unsafe { w.scrdv().bits(cfg.scrdv) });
|
||||
self.reg_block()
|
||||
.clkprescale()
|
||||
.write(|w| unsafe { w.bits(cfg.prescale_val as u32) });
|
||||
}
|
||||
|
||||
pub fn set_fill_word(&mut self, fill_word: Word) {
|
||||
self.fill_word = fill_word;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> {
|
||||
let val = spi_clk_config_from_div(div)?;
|
||||
self.cfg_clock(val);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cfg_mode(&mut self, mode: Mode) {
|
||||
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode);
|
||||
self.reg_block().ctrl0().modify(|_, w| {
|
||||
w.spo().bit(cpo_bit);
|
||||
w.sph().bit(cph_bit)
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fill_word(&self) -> Word {
|
||||
self.fill_word
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clear_tx_fifo(&mut self) {
|
||||
self.reg_block().fifo_clr().write(|w| w.txfifo().set_bit());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clear_rx_fifo(&mut self) {
|
||||
self.reg_block().fifo_clr().write(|w| w.rxfifo().set_bit());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn perid(&self) -> u32 {
|
||||
self.reg_block().perid().read().bits()
|
||||
}
|
||||
|
||||
/// Configure the hardware chip select given a hardware chip select ID.
|
||||
///
|
||||
/// The pin also needs to be configured to be used as a HW CS pin. This can be done
|
||||
/// by using the [configure_pin_as_hw_cs_pin] function which also returns the
|
||||
/// corresponding [HwChipSelectId].
|
||||
#[inline]
|
||||
pub fn cfg_hw_cs(&mut self, hw_cs: HwChipSelectId) {
|
||||
self.reg_block().ctrl1().modify(|_, w| {
|
||||
w.sod().clear_bit();
|
||||
unsafe {
|
||||
w.ss().bits(hw_cs as u8);
|
||||
}
|
||||
w
|
||||
});
|
||||
}
|
||||
|
||||
/// Disables the hardware chip select functionality. This can be used when performing
|
||||
/// external chip select handling, for example with GPIO pins.
|
||||
#[inline]
|
||||
pub fn cfg_hw_cs_disable(&mut self) {
|
||||
self.reg_block().ctrl1().modify(|_, w| {
|
||||
w.sod().set_bit();
|
||||
w
|
||||
});
|
||||
}
|
||||
|
||||
/// Utility function to configure all relevant transfer parameters in one go.
|
||||
/// This is useful if multiple devices with different clock and mode configurations
|
||||
/// are connected to one bus.
|
||||
pub fn cfg_transfer(&mut self, transfer_cfg: &TransferConfig) {
|
||||
if let Some(trans_clk_div) = transfer_cfg.clk_cfg {
|
||||
self.cfg_clock(trans_clk_div);
|
||||
}
|
||||
if let Some(mode) = transfer_cfg.mode {
|
||||
self.cfg_mode(mode);
|
||||
}
|
||||
self.blockmode = transfer_cfg.blockmode;
|
||||
self.reg_block().ctrl1().modify(|_, w| {
|
||||
if transfer_cfg.sod {
|
||||
w.sod().set_bit();
|
||||
} else if transfer_cfg.hw_cs.is_some() {
|
||||
w.sod().clear_bit();
|
||||
unsafe {
|
||||
w.ss().bits(transfer_cfg.hw_cs.unwrap() as u8);
|
||||
}
|
||||
} else {
|
||||
w.sod().clear_bit();
|
||||
}
|
||||
w.blockmode().bit(transfer_cfg.blockmode);
|
||||
w.bmstall().bit(transfer_cfg.bmstall)
|
||||
});
|
||||
}
|
||||
|
||||
fn flush_internal(&mut self) {
|
||||
let reg_block = self.reg_block();
|
||||
let mut status_reg = reg_block.status().read();
|
||||
while status_reg.tfe().bit_is_clear()
|
||||
|| status_reg.rne().bit_is_set()
|
||||
|| status_reg.busy().bit_is_set()
|
||||
{
|
||||
if status_reg.rne().bit_is_set() {
|
||||
self.read_fifo_unchecked();
|
||||
}
|
||||
status_reg = reg_block.status().read();
|
||||
}
|
||||
}
|
||||
|
||||
fn transfer_preparation(&mut self, words: &[Word]) -> Result<(), Infallible> {
|
||||
if words.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
self.flush_internal();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// The FIFO can hold a guaranteed amount of data, so we can pump it on transfer
|
||||
// initialization. Returns the amount of written bytes.
|
||||
fn initial_send_fifo_pumping_with_words(&mut self, words: &[Word]) -> usize {
|
||||
let reg_block = self.reg_block();
|
||||
if self.blockmode {
|
||||
reg_block.ctrl1().modify(|_, w| w.mtxpause().set_bit());
|
||||
}
|
||||
// Fill the first half of the write FIFO
|
||||
let mut current_write_idx = 0;
|
||||
let smaller_idx = core::cmp::min(FILL_DEPTH, words.len());
|
||||
for _ in 0..smaller_idx {
|
||||
if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall {
|
||||
self.write_fifo_unchecked(words[current_write_idx].into() | BMSTART_BMSTOP_MASK);
|
||||
} else {
|
||||
self.write_fifo_unchecked(words[current_write_idx].into());
|
||||
}
|
||||
current_write_idx += 1;
|
||||
}
|
||||
if self.blockmode {
|
||||
reg_block.ctrl1().modify(|_, w| w.mtxpause().clear_bit());
|
||||
}
|
||||
current_write_idx
|
||||
}
|
||||
|
||||
// The FIFO can hold a guaranteed amount of data, so we can pump it on transfer
|
||||
// initialization.
|
||||
fn initial_send_fifo_pumping_with_fill_words(&mut self, send_len: usize) -> usize {
|
||||
let reg_block = self.reg_block();
|
||||
if self.blockmode {
|
||||
reg_block.ctrl1().modify(|_, w| w.mtxpause().set_bit());
|
||||
}
|
||||
// Fill the first half of the write FIFO
|
||||
let mut current_write_idx = 0;
|
||||
let smaller_idx = core::cmp::min(FILL_DEPTH, send_len);
|
||||
for _ in 0..smaller_idx {
|
||||
if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall {
|
||||
self.write_fifo_unchecked(self.fill_word.into() | BMSTART_BMSTOP_MASK);
|
||||
} else {
|
||||
self.write_fifo_unchecked(self.fill_word.into());
|
||||
}
|
||||
current_write_idx += 1;
|
||||
}
|
||||
if self.blockmode {
|
||||
reg_block.ctrl1().modify(|_, w| w.mtxpause().clear_bit());
|
||||
}
|
||||
current_write_idx
|
||||
}
|
||||
}
|
||||
|
||||
impl<Word: WordProvider> SpiLowLevel for Spi<Word>
|
||||
where
|
||||
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn write_fifo(&mut self, data: u32) -> nb::Result<(), Infallible> {
|
||||
if self.reg_block().status().read().tnf().bit_is_clear() {
|
||||
return Err(nb::Error::WouldBlock);
|
||||
}
|
||||
self.write_fifo_unchecked(data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn write_fifo_unchecked(&mut self, data: u32) {
|
||||
self.reg_block().data().write(|w| unsafe { w.bits(data) });
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn read_fifo(&mut self) -> nb::Result<u32, Infallible> {
|
||||
if self.reg_block().status().read().rne().bit_is_clear() {
|
||||
return Err(nb::Error::WouldBlock);
|
||||
}
|
||||
Ok(self.read_fifo_unchecked())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn read_fifo_unchecked(&mut self) -> u32 {
|
||||
self.reg_block().data().read().bits()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Word: WordProvider> embedded_hal::spi::ErrorType for Spi<Word> {
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
impl<Word: WordProvider> embedded_hal::spi::SpiBus<Word> for Spi<Word>
|
||||
where
|
||||
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
||||
{
|
||||
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
||||
self.transfer_preparation(words)?;
|
||||
let mut current_read_idx = 0;
|
||||
let mut current_write_idx = self.initial_send_fifo_pumping_with_fill_words(words.len());
|
||||
loop {
|
||||
if current_read_idx < words.len() {
|
||||
words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
current_read_idx += 1;
|
||||
}
|
||||
if current_write_idx < words.len() {
|
||||
if current_write_idx == words.len() - 1 && self.bmstall {
|
||||
nb::block!(self.write_fifo(self.fill_word.into() | BMSTART_BMSTOP_MASK))?;
|
||||
} else {
|
||||
nb::block!(self.write_fifo(self.fill_word.into()))?;
|
||||
}
|
||||
current_write_idx += 1;
|
||||
}
|
||||
if current_read_idx >= words.len() && current_write_idx >= words.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
|
||||
self.transfer_preparation(words)?;
|
||||
let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words);
|
||||
while current_write_idx < words.len() {
|
||||
if current_write_idx == words.len() - 1 && self.bmstall {
|
||||
nb::block!(self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK))?;
|
||||
} else {
|
||||
nb::block!(self.write_fifo(words[current_write_idx].into()))?;
|
||||
}
|
||||
current_write_idx += 1;
|
||||
// Ignore received words.
|
||||
if self.reg_block().status().read().rne().bit_is_set() {
|
||||
self.clear_rx_fifo();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
|
||||
self.transfer_preparation(write)?;
|
||||
let mut current_read_idx = 0;
|
||||
let mut current_write_idx = self.initial_send_fifo_pumping_with_words(write);
|
||||
while current_read_idx < read.len() || current_write_idx < write.len() {
|
||||
if current_write_idx < write.len() {
|
||||
if current_write_idx == write.len() - 1 && self.bmstall {
|
||||
nb::block!(
|
||||
self.write_fifo(write[current_write_idx].into() | BMSTART_BMSTOP_MASK)
|
||||
)?;
|
||||
} else {
|
||||
nb::block!(self.write_fifo(write[current_write_idx].into()))?;
|
||||
}
|
||||
current_write_idx += 1;
|
||||
}
|
||||
if current_read_idx < read.len() {
|
||||
read[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
current_read_idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
||||
self.transfer_preparation(words)?;
|
||||
let mut current_read_idx = 0;
|
||||
let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words);
|
||||
|
||||
while current_read_idx < words.len() || current_write_idx < words.len() {
|
||||
if current_write_idx < words.len() {
|
||||
if current_write_idx == words.len() - 1 && self.bmstall {
|
||||
nb::block!(
|
||||
self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK)
|
||||
)?;
|
||||
} else {
|
||||
nb::block!(self.write_fifo(words[current_write_idx].into()))?;
|
||||
}
|
||||
current_write_idx += 1;
|
||||
}
|
||||
if current_read_idx < words.len() && current_read_idx < current_write_idx {
|
||||
words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
current_read_idx += 1;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
self.flush_internal();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Changing the word size also requires a type conversion
|
||||
impl From<Spi<u8>> for Spi<u16> {
|
||||
fn from(old_spi: Spi<u8>) -> Self {
|
||||
old_spi
|
||||
.reg_block()
|
||||
.ctrl0()
|
||||
.modify(|_, w| unsafe { w.size().bits(WordSize::SixteenBits as u8) });
|
||||
Spi {
|
||||
id: old_spi.id,
|
||||
reg_block: old_spi.reg_block,
|
||||
cfg: old_spi.cfg,
|
||||
blockmode: old_spi.blockmode,
|
||||
fill_word: Default::default(),
|
||||
bmstall: old_spi.bmstall,
|
||||
sys_clk: old_spi.sys_clk,
|
||||
word: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Spi<u16>> for Spi<u8> {
|
||||
fn from(old_spi: Spi<u16>) -> Self {
|
||||
old_spi
|
||||
.reg_block()
|
||||
.ctrl0()
|
||||
.modify(|_, w| unsafe { w.size().bits(WordSize::EightBits as u8) });
|
||||
Spi {
|
||||
id: old_spi.id,
|
||||
reg_block: old_spi.reg_block,
|
||||
cfg: old_spi.cfg,
|
||||
blockmode: old_spi.blockmode,
|
||||
fill_word: Default::default(),
|
||||
bmstall: old_spi.bmstall,
|
||||
sys_clk: old_spi.sys_clk,
|
||||
word: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub use vorago_shared_periphs::spi::pins_vor1x as pins;
|
||||
|
@@ -4,656 +4,6 @@
|
||||
//!
|
||||
//! - [MS and second tick implementation](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/timer-ticks.rs)
|
||||
//! - [Cascade feature example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/cascade.rs)
|
||||
pub use crate::InterruptConfig;
|
||||
use crate::{
|
||||
enable_nvic_interrupt,
|
||||
pac::{self, tim0},
|
||||
pins::{
|
||||
Pa0, Pa1, Pa10, Pa11, Pa12, Pa13, Pa14, Pa15, Pa2, Pa24, Pa25, Pa26, Pa27, Pa28, Pa29, Pa3,
|
||||
Pa30, Pa31, Pa4, Pa5, Pa6, Pa7, Pa8, Pa9, Pb0, Pb1, Pb10, Pb11, Pb12, Pb13, Pb14, Pb15,
|
||||
Pb16, Pb17, Pb18, Pb19, Pb2, Pb20, Pb21, Pb22, Pb23, Pb3, Pb4, Pb5, Pb6,
|
||||
},
|
||||
sealed::Sealed,
|
||||
time::Hertz,
|
||||
};
|
||||
use fugit::RateExtU32;
|
||||
use vorago_shared_periphs::{
|
||||
gpio::{Pin, PinId, PinIdProvider},
|
||||
ioconfig::regs::FunSel,
|
||||
pins::PinMarker,
|
||||
sysconfig::enable_peripheral_clock,
|
||||
PeripheralSelect,
|
||||
};
|
||||
pub use vorago_shared_periphs::timer::*;
|
||||
|
||||
/// Get the peripheral block of a TIM peripheral given the index.
|
||||
///
|
||||
/// This function panics if the given index is greater than 23.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This returns a direct handle to the peripheral block, which allows to circumvent ownership
|
||||
/// rules for the peripheral block. You have to ensure that the retrieved peripheral block is not
|
||||
/// used by any other software component.
|
||||
#[inline(always)]
|
||||
pub const unsafe fn get_tim_raw(tim_idx: usize) -> &'static pac::tim0::RegisterBlock {
|
||||
match tim_idx {
|
||||
0 => unsafe { &*pac::Tim0::ptr() },
|
||||
1 => unsafe { &*pac::Tim1::ptr() },
|
||||
2 => unsafe { &*pac::Tim2::ptr() },
|
||||
3 => unsafe { &*pac::Tim3::ptr() },
|
||||
4 => unsafe { &*pac::Tim4::ptr() },
|
||||
5 => unsafe { &*pac::Tim5::ptr() },
|
||||
6 => unsafe { &*pac::Tim6::ptr() },
|
||||
7 => unsafe { &*pac::Tim7::ptr() },
|
||||
8 => unsafe { &*pac::Tim8::ptr() },
|
||||
9 => unsafe { &*pac::Tim9::ptr() },
|
||||
10 => unsafe { &*pac::Tim10::ptr() },
|
||||
11 => unsafe { &*pac::Tim11::ptr() },
|
||||
12 => unsafe { &*pac::Tim12::ptr() },
|
||||
13 => unsafe { &*pac::Tim13::ptr() },
|
||||
14 => unsafe { &*pac::Tim14::ptr() },
|
||||
15 => unsafe { &*pac::Tim15::ptr() },
|
||||
16 => unsafe { &*pac::Tim16::ptr() },
|
||||
17 => unsafe { &*pac::Tim17::ptr() },
|
||||
18 => unsafe { &*pac::Tim18::ptr() },
|
||||
19 => unsafe { &*pac::Tim19::ptr() },
|
||||
20 => unsafe { &*pac::Tim20::ptr() },
|
||||
21 => unsafe { &*pac::Tim21::ptr() },
|
||||
22 => unsafe { &*pac::Tim22::ptr() },
|
||||
23 => unsafe { &*pac::Tim23::ptr() },
|
||||
_ => {
|
||||
panic!("invalid alarm timer index")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Defintions
|
||||
//==================================================================================================
|
||||
|
||||
/// Interrupt events
|
||||
pub enum Event {
|
||||
/// Timer timed out / count down ended
|
||||
TimeOut,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct CascadeCtrl {
|
||||
/// Enable Cascade 0 signal active as a requirement for counting
|
||||
pub enb_start_src_csd0: bool,
|
||||
/// Invert Cascade 0, making it active low
|
||||
pub inv_csd0: bool,
|
||||
/// Enable Cascade 1 signal active as a requirement for counting
|
||||
pub enb_start_src_csd1: bool,
|
||||
/// Invert Cascade 1, making it active low
|
||||
pub inv_csd1: bool,
|
||||
/// Specify required operation if both Cascade 0 and Cascade 1 are active.
|
||||
/// 0 is a logical AND of both cascade signals, 1 is a logical OR
|
||||
pub dual_csd_op: bool,
|
||||
/// Enable trigger mode for Cascade 0. In trigger mode, couting will start with the selected
|
||||
/// cascade signal active, but once the counter is active, cascade control will be ignored
|
||||
pub trg_csd0: bool,
|
||||
/// Trigger mode, identical to [`trg_csd0`](CascadeCtrl) but for Cascade 1
|
||||
pub trg_csd1: bool,
|
||||
/// Enable Cascade 2 signal active as a requirement to stop counting. This mode is similar
|
||||
/// to the REQ_STOP control bit, but signalled by a Cascade source
|
||||
pub enb_stop_src_csd2: bool,
|
||||
/// Invert Cascade 2, making it active low
|
||||
pub inv_csd2: bool,
|
||||
/// The counter is automatically disabled if the corresponding Cascade 2 level-sensitive input
|
||||
/// souce is active when the count reaches 0. If the counter is not 0, the cascade control is
|
||||
/// ignored
|
||||
pub trg_csd2: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum CascadeSel {
|
||||
Csd0 = 0,
|
||||
Csd1 = 1,
|
||||
Csd2 = 2,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct InvalidCascadeSourceId;
|
||||
|
||||
/// The numbers are the base numbers for bundles like PORTA, PORTB or TIM
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum CascadeSource {
|
||||
PortA(u8),
|
||||
PortB(u8),
|
||||
Tim(u8),
|
||||
RamSbe = 96,
|
||||
RamMbe = 97,
|
||||
RomSbe = 98,
|
||||
RomMbe = 99,
|
||||
Txev = 100,
|
||||
ClockDivider(u8),
|
||||
}
|
||||
|
||||
impl CascadeSource {
|
||||
fn id(&self) -> Result<u8, InvalidCascadeSourceId> {
|
||||
let port_check = |base: u8, id: u8, len: u8| {
|
||||
if id > len - 1 {
|
||||
return Err(InvalidCascadeSourceId);
|
||||
}
|
||||
Ok(base + id)
|
||||
};
|
||||
match self {
|
||||
CascadeSource::PortA(id) => port_check(0, *id, 32),
|
||||
CascadeSource::PortB(id) => port_check(32, *id, 32),
|
||||
CascadeSource::Tim(id) => port_check(64, *id, 24),
|
||||
CascadeSource::RamSbe => Ok(96),
|
||||
CascadeSource::RamMbe => Ok(97),
|
||||
CascadeSource::RomSbe => Ok(98),
|
||||
CascadeSource::RomMbe => Ok(99),
|
||||
CascadeSource::Txev => Ok(100),
|
||||
CascadeSource::ClockDivider(id) => port_check(120, *id, 8),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Valid TIM and PIN combinations
|
||||
//==================================================================================================
|
||||
|
||||
pub trait TimPin: PinMarker {
|
||||
const PIN_ID: PinId;
|
||||
const FUN_SEL: FunSel;
|
||||
const TIM_ID: TimId;
|
||||
}
|
||||
|
||||
pub trait TimMarker: Sealed {
|
||||
// TIM ID ranging from 0 to 23 for 24 TIM peripherals
|
||||
const ID: TimId;
|
||||
}
|
||||
|
||||
macro_rules! tim_marker {
|
||||
($TIMX:path, $ID:expr) => {
|
||||
impl TimMarker for $TIMX {
|
||||
const ID: TimId = TimId($ID);
|
||||
}
|
||||
|
||||
unsafe impl TimRegInterface for $TIMX {
|
||||
fn raw_id(&self) -> u8 {
|
||||
Self::ID.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Sealed for $TIMX {}
|
||||
};
|
||||
}
|
||||
|
||||
tim_marker!(pac::Tim0, 0);
|
||||
tim_marker!(pac::Tim1, 1);
|
||||
tim_marker!(pac::Tim2, 2);
|
||||
tim_marker!(pac::Tim3, 3);
|
||||
tim_marker!(pac::Tim4, 4);
|
||||
tim_marker!(pac::Tim5, 5);
|
||||
tim_marker!(pac::Tim6, 6);
|
||||
tim_marker!(pac::Tim7, 7);
|
||||
tim_marker!(pac::Tim8, 8);
|
||||
tim_marker!(pac::Tim9, 9);
|
||||
tim_marker!(pac::Tim10, 10);
|
||||
tim_marker!(pac::Tim11, 11);
|
||||
tim_marker!(pac::Tim12, 12);
|
||||
tim_marker!(pac::Tim13, 13);
|
||||
tim_marker!(pac::Tim14, 14);
|
||||
tim_marker!(pac::Tim15, 15);
|
||||
tim_marker!(pac::Tim16, 16);
|
||||
tim_marker!(pac::Tim17, 17);
|
||||
tim_marker!(pac::Tim18, 18);
|
||||
tim_marker!(pac::Tim19, 19);
|
||||
tim_marker!(pac::Tim20, 20);
|
||||
tim_marker!(pac::Tim21, 21);
|
||||
tim_marker!(pac::Tim22, 22);
|
||||
tim_marker!(pac::Tim23, 23);
|
||||
|
||||
pub trait ValidTimAndPin<Pin: TimPin, Tim: TimMarker>: Sealed {}
|
||||
|
||||
macro_rules! pin_and_tim {
|
||||
($Px:ident, $FunSel:path, $ID:expr) => {
|
||||
impl TimPin for Pin<$Px>
|
||||
where
|
||||
$Px: PinIdProvider,
|
||||
{
|
||||
const PIN_ID: PinId = $Px::ID;
|
||||
const FUN_SEL: FunSel = $FunSel;
|
||||
const TIM_ID: TimId = TimId($ID);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pin_and_tim!(Pa0, FunSel::Sel1, 0);
|
||||
pin_and_tim!(Pa1, FunSel::Sel1, 1);
|
||||
pin_and_tim!(Pa2, FunSel::Sel1, 2);
|
||||
pin_and_tim!(Pa3, FunSel::Sel1, 3);
|
||||
pin_and_tim!(Pa4, FunSel::Sel1, 4);
|
||||
pin_and_tim!(Pa5, FunSel::Sel1, 5);
|
||||
pin_and_tim!(Pa6, FunSel::Sel1, 6);
|
||||
pin_and_tim!(Pa7, FunSel::Sel1, 7);
|
||||
pin_and_tim!(Pa8, FunSel::Sel1, 8);
|
||||
pin_and_tim!(Pa9, FunSel::Sel1, 9);
|
||||
pin_and_tim!(Pa10, FunSel::Sel1, 10);
|
||||
pin_and_tim!(Pa11, FunSel::Sel1, 11);
|
||||
pin_and_tim!(Pa12, FunSel::Sel1, 12);
|
||||
pin_and_tim!(Pa13, FunSel::Sel1, 13);
|
||||
pin_and_tim!(Pa14, FunSel::Sel1, 14);
|
||||
pin_and_tim!(Pa15, FunSel::Sel1, 15);
|
||||
|
||||
pin_and_tim!(Pa24, FunSel::Sel2, 16);
|
||||
pin_and_tim!(Pa25, FunSel::Sel2, 17);
|
||||
pin_and_tim!(Pa26, FunSel::Sel2, 18);
|
||||
pin_and_tim!(Pa27, FunSel::Sel2, 19);
|
||||
pin_and_tim!(Pa28, FunSel::Sel2, 20);
|
||||
pin_and_tim!(Pa29, FunSel::Sel2, 21);
|
||||
pin_and_tim!(Pa30, FunSel::Sel2, 22);
|
||||
pin_and_tim!(Pa31, FunSel::Sel2, 23);
|
||||
|
||||
pin_and_tim!(Pb0, FunSel::Sel3, 0);
|
||||
pin_and_tim!(Pb1, FunSel::Sel3, 1);
|
||||
pin_and_tim!(Pb2, FunSel::Sel3, 2);
|
||||
pin_and_tim!(Pb3, FunSel::Sel3, 3);
|
||||
pin_and_tim!(Pb4, FunSel::Sel3, 4);
|
||||
pin_and_tim!(Pb5, FunSel::Sel3, 5);
|
||||
pin_and_tim!(Pb6, FunSel::Sel3, 6);
|
||||
|
||||
pin_and_tim!(Pb10, FunSel::Sel3, 10);
|
||||
pin_and_tim!(Pb11, FunSel::Sel3, 11);
|
||||
pin_and_tim!(Pb12, FunSel::Sel3, 12);
|
||||
pin_and_tim!(Pb13, FunSel::Sel3, 13);
|
||||
pin_and_tim!(Pb14, FunSel::Sel3, 14);
|
||||
pin_and_tim!(Pb15, FunSel::Sel3, 15);
|
||||
pin_and_tim!(Pb16, FunSel::Sel3, 16);
|
||||
pin_and_tim!(Pb17, FunSel::Sel3, 17);
|
||||
pin_and_tim!(Pb18, FunSel::Sel3, 18);
|
||||
pin_and_tim!(Pb19, FunSel::Sel3, 19);
|
||||
pin_and_tim!(Pb20, FunSel::Sel3, 20);
|
||||
pin_and_tim!(Pb21, FunSel::Sel3, 21);
|
||||
pin_and_tim!(Pb22, FunSel::Sel3, 22);
|
||||
pin_and_tim!(Pb23, FunSel::Sel3, 23);
|
||||
|
||||
//==================================================================================================
|
||||
// Register Interface for TIM registers and TIM pins
|
||||
//==================================================================================================
|
||||
|
||||
pub type TimRegBlock = tim0::RegisterBlock;
|
||||
|
||||
/// Register interface.
|
||||
///
|
||||
/// This interface provides valid TIM pins a way to access their corresponding TIM
|
||||
/// registers
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Users should only implement the [Self::raw_id] function. No default function
|
||||
/// implementations should be overridden. The implementing type must also have
|
||||
/// "control" over the corresponding pin ID, i.e. it must guarantee that a each
|
||||
/// pin ID is a singleton.
|
||||
pub unsafe trait TimRegInterface {
|
||||
fn raw_id(&self) -> u8;
|
||||
|
||||
const PORT_BASE: *const tim0::RegisterBlock = pac::Tim0::ptr() as *const _;
|
||||
|
||||
/// All 24 TIM blocks are identical. This helper functions returns the correct
|
||||
/// memory mapped peripheral depending on the TIM ID.
|
||||
#[inline(always)]
|
||||
fn reg_block(&self) -> &TimRegBlock {
|
||||
unsafe { &*Self::PORT_BASE.offset(self.raw_id() as isize) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mask_32(&self) -> u32 {
|
||||
1 << self.raw_id()
|
||||
}
|
||||
|
||||
/// Clear the reset bit of the TIM, holding it in reset
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Only the bit related to the corresponding TIM peripheral is modified
|
||||
#[inline]
|
||||
fn assert_tim_reset(&self) {
|
||||
unsafe {
|
||||
va108xx::Peripherals::steal()
|
||||
.sysconfig
|
||||
.tim_reset()
|
||||
.modify(|r, w| w.bits(r.bits() & !self.mask_32()));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deassert_tim_reset(&self) {
|
||||
unsafe {
|
||||
va108xx::Peripherals::steal()
|
||||
.sysconfig
|
||||
.tim_reset()
|
||||
.modify(|r, w| w.bits(r.bits() | self.mask_32()));
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_tim_reset_for_cycles(&self, cycles: u32) {
|
||||
self.assert_tim_reset();
|
||||
cortex_m::asm::delay(cycles);
|
||||
self.deassert_tim_reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct TimId(u8);
|
||||
|
||||
unsafe impl TimRegInterface for TimId {
|
||||
fn raw_id(&self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Timers
|
||||
//==================================================================================================
|
||||
|
||||
/// Hardware timers
|
||||
pub struct CountdownTimer {
|
||||
tim: TimId,
|
||||
curr_freq: Hertz,
|
||||
sys_clk: Hertz,
|
||||
rst_val: u32,
|
||||
last_cnt: u32,
|
||||
}
|
||||
|
||||
unsafe impl TimRegInterface for CountdownTimer {
|
||||
fn raw_id(&self) -> u8 {
|
||||
self.tim.0
|
||||
}
|
||||
}
|
||||
|
||||
impl CountdownTimer {
|
||||
/// Create a countdown timer structure for a given TIM peripheral.
|
||||
///
|
||||
/// This does not enable the timer. You can use the [Self::load], [Self::start],
|
||||
/// [Self::enable_interrupt] and [Self::enable] API to set up and configure the countdown
|
||||
/// timer.
|
||||
pub fn new<Tim: TimMarker + TimRegInterface>(sys_clk: Hertz, tim: Tim) -> Self {
|
||||
enable_tim_clk(Tim::ID.raw_id());
|
||||
tim.assert_tim_reset_for_cycles(2);
|
||||
CountdownTimer {
|
||||
tim: Tim::ID,
|
||||
sys_clk,
|
||||
rst_val: 0,
|
||||
curr_freq: 0.Hz(),
|
||||
last_cnt: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn enable(&mut self) {
|
||||
self.tim
|
||||
.reg_block()
|
||||
.enable()
|
||||
.write(|w| unsafe { w.bits(1) });
|
||||
}
|
||||
|
||||
pub fn enable_interrupt(&mut self, irq_cfg: InterruptConfig) {
|
||||
if irq_cfg.route {
|
||||
let irqsel = unsafe { pac::Irqsel::steal() };
|
||||
enable_peripheral_clock(PeripheralSelect::Irqsel);
|
||||
irqsel
|
||||
.tim0(self.raw_id() as usize)
|
||||
.write(|w| unsafe { w.bits(irq_cfg.id as u32) });
|
||||
}
|
||||
if irq_cfg.enable_in_nvic {
|
||||
unsafe { enable_nvic_interrupt(irq_cfg.id) };
|
||||
}
|
||||
self.tim
|
||||
.reg_block()
|
||||
.ctrl()
|
||||
.modify(|_, w| w.irq_enb().set_bit());
|
||||
}
|
||||
|
||||
/// Calls [Self::load] to configure the specified frequency and then calls [Self::enable].
|
||||
pub fn start(&mut self, frequency: impl Into<Hertz>) {
|
||||
self.load(frequency);
|
||||
self.enable();
|
||||
}
|
||||
|
||||
/// Return `Ok` if the timer has wrapped. Peripheral will automatically clear the
|
||||
/// flag and restart the time if configured correctly
|
||||
pub fn wait(&mut self) -> nb::Result<(), void::Void> {
|
||||
let cnt = self.tim.reg_block().cnt_value().read().bits();
|
||||
if (cnt > self.last_cnt) || cnt == 0 {
|
||||
self.last_cnt = self.rst_val;
|
||||
Ok(())
|
||||
} else {
|
||||
self.last_cnt = cnt;
|
||||
Err(nb::Error::WouldBlock)
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the count down timer with a timeout but do not start it.
|
||||
pub fn load(&mut self, timeout: impl Into<Hertz>) {
|
||||
self.tim
|
||||
.reg_block()
|
||||
.ctrl()
|
||||
.modify(|_, w| w.enable().clear_bit());
|
||||
self.curr_freq = timeout.into();
|
||||
self.rst_val = self.sys_clk.raw() / self.curr_freq.raw();
|
||||
self.set_reload(self.rst_val);
|
||||
self.set_count(self.rst_val);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set_reload(&mut self, val: u32) {
|
||||
self.tim
|
||||
.reg_block()
|
||||
.rst_value()
|
||||
.write(|w| unsafe { w.bits(val) });
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set_count(&mut self, val: u32) {
|
||||
self.tim
|
||||
.reg_block()
|
||||
.cnt_value()
|
||||
.write(|w| unsafe { w.bits(val) });
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn count(&self) -> u32 {
|
||||
self.tim.reg_block().cnt_value().read().bits()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn disable(&mut self) {
|
||||
self.tim
|
||||
.reg_block()
|
||||
.enable()
|
||||
.write(|w| unsafe { w.bits(0) });
|
||||
}
|
||||
|
||||
/// Disable the counter, setting both enable and active bit to 0
|
||||
pub fn auto_disable(self, enable: bool) -> Self {
|
||||
if enable {
|
||||
self.tim
|
||||
.reg_block()
|
||||
.ctrl()
|
||||
.modify(|_, w| w.auto_disable().set_bit());
|
||||
} else {
|
||||
self.tim
|
||||
.reg_block()
|
||||
.ctrl()
|
||||
.modify(|_, w| w.auto_disable().clear_bit());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// This option only applies when the Auto-Disable functionality is 0.
|
||||
///
|
||||
/// The active bit is changed to 0 when count reaches 0, but the counter stays
|
||||
/// enabled. When Auto-Disable is 1, Auto-Deactivate is implied
|
||||
pub fn auto_deactivate(self, enable: bool) -> Self {
|
||||
if enable {
|
||||
self.tim
|
||||
.reg_block()
|
||||
.ctrl()
|
||||
.modify(|_, w| w.auto_deactivate().set_bit());
|
||||
} else {
|
||||
self.tim
|
||||
.reg_block()
|
||||
.ctrl()
|
||||
.modify(|_, w| w.auto_deactivate().clear_bit());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure the cascade parameters
|
||||
pub fn cascade_control(&mut self, ctrl: CascadeCtrl) {
|
||||
self.tim.reg_block().csd_ctrl().write(|w| {
|
||||
w.csden0().bit(ctrl.enb_start_src_csd0);
|
||||
w.csdinv0().bit(ctrl.inv_csd0);
|
||||
w.csden1().bit(ctrl.enb_start_src_csd1);
|
||||
w.csdinv1().bit(ctrl.inv_csd1);
|
||||
w.dcasop().bit(ctrl.dual_csd_op);
|
||||
w.csdtrg0().bit(ctrl.trg_csd0);
|
||||
w.csdtrg1().bit(ctrl.trg_csd1);
|
||||
w.csden2().bit(ctrl.enb_stop_src_csd2);
|
||||
w.csdinv2().bit(ctrl.inv_csd2);
|
||||
w.csdtrg2().bit(ctrl.trg_csd2)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn cascade_0_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
|
||||
let id = src.id()?;
|
||||
self.tim
|
||||
.reg_block()
|
||||
.cascade0()
|
||||
.write(|w| unsafe { w.cassel().bits(id) });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cascade_1_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
|
||||
let id = src.id()?;
|
||||
self.tim
|
||||
.reg_block()
|
||||
.cascade1()
|
||||
.write(|w| unsafe { w.cassel().bits(id) });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cascade_2_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
|
||||
let id = src.id()?;
|
||||
self.tim
|
||||
.reg_block()
|
||||
.cascade2()
|
||||
.write(|w| unsafe { w.cassel().bits(id) });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn curr_freq(&self) -> Hertz {
|
||||
self.curr_freq
|
||||
}
|
||||
|
||||
/// This function only clears the interrupt enable bit.
|
||||
///
|
||||
/// It does not mask the interrupt in the NVIC or un-route the IRQ.
|
||||
#[inline(always)]
|
||||
pub fn disable_interrupt(&mut self) {
|
||||
self.tim
|
||||
.reg_block()
|
||||
.ctrl()
|
||||
.modify(|_, w| w.irq_enb().clear_bit());
|
||||
}
|
||||
|
||||
/// Disables the TIM and the dedicated TIM clock.
|
||||
pub fn stop_with_clock_disable(self) {
|
||||
self.tim
|
||||
.reg_block()
|
||||
.ctrl()
|
||||
.write(|w| w.enable().clear_bit());
|
||||
let syscfg = unsafe { va108xx::Sysconfig::steal() };
|
||||
syscfg
|
||||
.tim_clk_enable()
|
||||
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << self.raw_id())) });
|
||||
}
|
||||
}
|
||||
|
||||
/// CountDown implementation for TIMx
|
||||
impl CountdownTimer {}
|
||||
|
||||
//==================================================================================================
|
||||
// Delay implementations
|
||||
//==================================================================================================
|
||||
//
|
||||
impl embedded_hal::delay::DelayNs for CountdownTimer {
|
||||
fn delay_ns(&mut self, ns: u32) {
|
||||
let ticks = (u64::from(ns)) * (u64::from(self.sys_clk.raw())) / 1_000_000_000;
|
||||
|
||||
let full_cycles = ticks >> 32;
|
||||
let mut last_count;
|
||||
let mut new_count;
|
||||
if full_cycles > 0 {
|
||||
self.set_reload(u32::MAX);
|
||||
self.set_count(u32::MAX);
|
||||
self.enable();
|
||||
|
||||
for _ in 0..full_cycles {
|
||||
// Always ensure that both values are the same at the start.
|
||||
new_count = self.count();
|
||||
last_count = new_count;
|
||||
loop {
|
||||
new_count = self.count();
|
||||
if new_count == 0 {
|
||||
// Wait till timer has wrapped.
|
||||
while self.count() == 0 {
|
||||
cortex_m::asm::nop()
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Timer has definitely wrapped.
|
||||
if new_count > last_count {
|
||||
break;
|
||||
}
|
||||
last_count = new_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
let ticks = (ticks & u32::MAX as u64) as u32;
|
||||
self.disable();
|
||||
if ticks > 1 {
|
||||
self.set_reload(ticks);
|
||||
self.set_count(ticks);
|
||||
self.enable();
|
||||
last_count = ticks;
|
||||
|
||||
loop {
|
||||
new_count = self.count();
|
||||
if new_count == 0 || (new_count > last_count) {
|
||||
break;
|
||||
}
|
||||
last_count = new_count;
|
||||
}
|
||||
}
|
||||
|
||||
self.disable();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn enable_tim_clk(idx: u8) {
|
||||
let syscfg = unsafe { va108xx::Sysconfig::steal() };
|
||||
syscfg
|
||||
.tim_clk_enable()
|
||||
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << idx)) });
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn disable_tim_clk(idx: u8) {
|
||||
let syscfg = unsafe { va108xx::Sysconfig::steal() };
|
||||
syscfg
|
||||
.tim_clk_enable()
|
||||
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << idx)) });
|
||||
}
|
||||
pub use vorago_shared_periphs::timer::regs;
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,441 +0,0 @@
|
||||
//! # Async UART reception functionality for the VA416xx family.
|
||||
//!
|
||||
//! This module provides the [RxAsync] and [RxAsyncOverwriting] struct which both implement the
|
||||
//! [embedded_io_async::Read] trait.
|
||||
//! This trait allows for asynchronous reception of data streams. Please note that this module does
|
||||
//! not specify/declare the interrupt handlers which must be provided for async support to work.
|
||||
//! However, it provides two interrupt handlers:
|
||||
//!
|
||||
//! - [on_interrupt_rx]
|
||||
//! - [on_interrupt_rx_overwriting]
|
||||
//!
|
||||
//! The first two are used for the [RxAsync] struct, while the latter two are used with the
|
||||
//! [RxAsyncOverwriting] struct. The later two will overwrite old values in the used ring buffer.
|
||||
//!
|
||||
//! Error handling is performed in the user interrupt handler by checking the [AsyncUartErrors]
|
||||
//! structure returned by the interrupt handlers.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! - [Async UART RX example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-rx.rs)
|
||||
use core::{cell::RefCell, convert::Infallible, future::Future, sync::atomic::Ordering};
|
||||
|
||||
use critical_section::Mutex;
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use embedded_io::ErrorType;
|
||||
use portable_atomic::AtomicBool;
|
||||
use va108xx::uarta as uart_base;
|
||||
|
||||
use super::{Rx, UartErrors, UartId};
|
||||
|
||||
static UART_RX_WAKERS: [AtomicWaker; 2] = [const { AtomicWaker::new() }; 2];
|
||||
static RX_READ_ACTIVE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
|
||||
static RX_HAS_DATA: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
|
||||
|
||||
struct RxFuture {
|
||||
uart_idx: usize,
|
||||
}
|
||||
|
||||
impl RxFuture {
|
||||
pub fn new(rx: &mut Rx) -> Self {
|
||||
RX_READ_ACTIVE[rx.0 as usize].store(true, Ordering::Relaxed);
|
||||
Self {
|
||||
uart_idx: rx.0 as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for RxFuture {
|
||||
type Output = Result<(), Infallible>;
|
||||
|
||||
fn poll(
|
||||
self: core::pin::Pin<&mut Self>,
|
||||
cx: &mut core::task::Context<'_>,
|
||||
) -> core::task::Poll<Self::Output> {
|
||||
UART_RX_WAKERS[self.uart_idx].register(cx.waker());
|
||||
if RX_HAS_DATA[self.uart_idx].load(Ordering::Relaxed) {
|
||||
return core::task::Poll::Ready(Ok(()));
|
||||
}
|
||||
core::task::Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct AsyncUartErrors {
|
||||
/// Queue has overflowed, data might have been lost.
|
||||
pub queue_overflow: bool,
|
||||
/// UART errors.
|
||||
pub uart_errors: UartErrors,
|
||||
}
|
||||
|
||||
fn on_interrupt_handle_rx_errors(uart: &'static uart_base::RegisterBlock) -> Option<UartErrors> {
|
||||
let rx_status = uart.rxstatus().read();
|
||||
if rx_status.rxovr().bit_is_set()
|
||||
|| rx_status.rxfrm().bit_is_set()
|
||||
|| rx_status.rxpar().bit_is_set()
|
||||
{
|
||||
let mut errors_val = UartErrors::default();
|
||||
|
||||
if rx_status.rxovr().bit_is_set() {
|
||||
errors_val.overflow = true;
|
||||
}
|
||||
if rx_status.rxfrm().bit_is_set() {
|
||||
errors_val.framing = true;
|
||||
}
|
||||
if rx_status.rxpar().bit_is_set() {
|
||||
errors_val.parity = true;
|
||||
}
|
||||
return Some(errors_val);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn on_interrupt_rx_common_post_processing(
|
||||
id: UartId,
|
||||
rx_enabled: bool,
|
||||
read_some_data: bool,
|
||||
irq_end: u32,
|
||||
) -> Option<UartErrors> {
|
||||
let idx = id as usize;
|
||||
if read_some_data {
|
||||
RX_HAS_DATA[idx].store(true, Ordering::Relaxed);
|
||||
if RX_READ_ACTIVE[idx].load(Ordering::Relaxed) {
|
||||
UART_RX_WAKERS[idx].wake();
|
||||
}
|
||||
}
|
||||
|
||||
let mut errors = None;
|
||||
let uart_regs = unsafe { id.reg_block() };
|
||||
// Check for RX errors
|
||||
if rx_enabled {
|
||||
errors = on_interrupt_handle_rx_errors(uart_regs);
|
||||
}
|
||||
|
||||
// Clear the interrupt status bits
|
||||
uart_regs.irq_clr().write(|w| unsafe { w.bits(irq_end) });
|
||||
errors
|
||||
}
|
||||
|
||||
/// Interrupt handler with overwriting behaviour when the ring buffer is full.
|
||||
///
|
||||
/// Should be called in the user interrupt handler to enable
|
||||
/// asynchronous reception. This variant will overwrite old data in the ring buffer in case
|
||||
/// the ring buffer is full.
|
||||
pub fn on_interrupt_rx_overwriting<const N: usize>(
|
||||
bank: UartId,
|
||||
prod: &mut heapless::spsc::Producer<u8, N>,
|
||||
shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
|
||||
) -> Result<(), AsyncUartErrors> {
|
||||
on_interrupt_rx_async_heapless_queue_overwriting(bank, prod, shared_consumer)
|
||||
}
|
||||
|
||||
pub fn on_interrupt_rx_async_heapless_queue_overwriting<const N: usize>(
|
||||
bank: UartId,
|
||||
prod: &mut heapless::spsc::Producer<u8, N>,
|
||||
shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
|
||||
) -> Result<(), AsyncUartErrors> {
|
||||
let uart_regs = unsafe { bank.reg_block() };
|
||||
let irq_end = uart_regs.irq_end().read();
|
||||
let enb_status = uart_regs.enable().read();
|
||||
let rx_enabled = enb_status.rxenable().bit_is_set();
|
||||
let mut read_some_data = false;
|
||||
let mut queue_overflow = false;
|
||||
|
||||
// Half-Full interrupt. We have a guaranteed amount of data we can read.
|
||||
if irq_end.irq_rx().bit_is_set() {
|
||||
let available_bytes = uart_regs.rxfifoirqtrg().read().bits() as usize;
|
||||
|
||||
// If this interrupt bit is set, the trigger level is available at the very least.
|
||||
// Read everything as fast as possible
|
||||
for _ in 0..available_bytes {
|
||||
let byte = uart_regs.data().read().bits();
|
||||
if !prod.ready() {
|
||||
queue_overflow = true;
|
||||
critical_section::with(|cs| {
|
||||
let mut cons_ref = shared_consumer.borrow(cs).borrow_mut();
|
||||
cons_ref.as_mut().unwrap().dequeue();
|
||||
});
|
||||
}
|
||||
prod.enqueue(byte as u8).ok();
|
||||
}
|
||||
read_some_data = true;
|
||||
}
|
||||
|
||||
// Timeout, empty the FIFO completely.
|
||||
if irq_end.irq_rx_to().bit_is_set() {
|
||||
while uart_regs.rxstatus().read().rdavl().bit_is_set() {
|
||||
// While there is data in the FIFO, write it into the reception buffer
|
||||
let byte = uart_regs.data().read().bits();
|
||||
if !prod.ready() {
|
||||
queue_overflow = true;
|
||||
critical_section::with(|cs| {
|
||||
let mut cons_ref = shared_consumer.borrow(cs).borrow_mut();
|
||||
cons_ref.as_mut().unwrap().dequeue();
|
||||
});
|
||||
}
|
||||
prod.enqueue(byte as u8).ok();
|
||||
}
|
||||
read_some_data = true;
|
||||
}
|
||||
|
||||
let uart_errors =
|
||||
on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data, irq_end.bits());
|
||||
if uart_errors.is_some() || queue_overflow {
|
||||
return Err(AsyncUartErrors {
|
||||
queue_overflow,
|
||||
uart_errors: uart_errors.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Interrupt handler for asynchronous RX operations.
|
||||
///
|
||||
/// Should be called in the user interrupt handler to enable asynchronous reception.
|
||||
pub fn on_interrupt_rx<const N: usize>(
|
||||
bank: UartId,
|
||||
prod: &mut heapless::spsc::Producer<'_, u8, N>,
|
||||
) -> Result<(), AsyncUartErrors> {
|
||||
on_interrupt_rx_async_heapless_queue(bank, prod)
|
||||
}
|
||||
|
||||
pub fn on_interrupt_rx_async_heapless_queue<const N: usize>(
|
||||
bank: UartId,
|
||||
prod: &mut heapless::spsc::Producer<'_, u8, N>,
|
||||
) -> Result<(), AsyncUartErrors> {
|
||||
let uart = unsafe { bank.reg_block() };
|
||||
let irq_end = uart.irq_end().read();
|
||||
let enb_status = uart.enable().read();
|
||||
let rx_enabled = enb_status.rxenable().bit_is_set();
|
||||
let mut read_some_data = false;
|
||||
let mut queue_overflow = false;
|
||||
|
||||
// Half-Full interrupt. We have a guaranteed amount of data we can read.
|
||||
if irq_end.irq_rx().bit_is_set() {
|
||||
let available_bytes = uart.rxfifoirqtrg().read().bits() as usize;
|
||||
|
||||
// If this interrupt bit is set, the trigger level is available at the very least.
|
||||
// Read everything as fast as possible
|
||||
for _ in 0..available_bytes {
|
||||
let byte = uart.data().read().bits();
|
||||
if !prod.ready() {
|
||||
queue_overflow = true;
|
||||
}
|
||||
prod.enqueue(byte as u8).ok();
|
||||
}
|
||||
read_some_data = true;
|
||||
}
|
||||
|
||||
// Timeout, empty the FIFO completely.
|
||||
if irq_end.irq_rx_to().bit_is_set() {
|
||||
while uart.rxstatus().read().rdavl().bit_is_set() {
|
||||
// While there is data in the FIFO, write it into the reception buffer
|
||||
let byte = uart.data().read().bits();
|
||||
if !prod.ready() {
|
||||
queue_overflow = true;
|
||||
}
|
||||
prod.enqueue(byte as u8).ok();
|
||||
}
|
||||
read_some_data = true;
|
||||
}
|
||||
|
||||
let uart_errors =
|
||||
on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data, irq_end.bits());
|
||||
if uart_errors.is_some() || queue_overflow {
|
||||
return Err(AsyncUartErrors {
|
||||
queue_overflow,
|
||||
uart_errors: uart_errors.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct ActiveReadGuard(usize);
|
||||
|
||||
impl Drop for ActiveReadGuard {
|
||||
fn drop(&mut self) {
|
||||
RX_READ_ACTIVE[self.0].store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
struct RxAsyncInner<const N: usize> {
|
||||
rx: Rx,
|
||||
pub queue: heapless::spsc::Consumer<'static, u8, N>,
|
||||
}
|
||||
|
||||
/// Core data structure to allow asynchronous UART reception.
|
||||
///
|
||||
/// If the ring buffer becomes full, data will be lost.
|
||||
pub struct RxAsync<const N: usize>(Option<RxAsyncInner<N>>);
|
||||
|
||||
impl<const N: usize> ErrorType for RxAsync<N> {
|
||||
/// Error reporting is done using the result of the interrupt functions.
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
fn stop_async_rx(rx: &mut Rx) {
|
||||
rx.disable_interrupts();
|
||||
rx.disable();
|
||||
rx.clear_fifo();
|
||||
}
|
||||
|
||||
impl<const N: usize> RxAsync<N> {
|
||||
/// Create a new asynchronous receiver.
|
||||
///
|
||||
/// The passed [heapless::spsc::Consumer] will be used to asynchronously receive data which
|
||||
/// is filled by the interrupt handler [on_interrupt_rx].
|
||||
pub fn new(mut rx: Rx, queue: heapless::spsc::Consumer<'static, u8, N>) -> Self {
|
||||
rx.disable_interrupts();
|
||||
rx.disable();
|
||||
rx.clear_fifo();
|
||||
// Enable those together.
|
||||
critical_section::with(|_| {
|
||||
rx.enable_interrupts();
|
||||
rx.enable();
|
||||
});
|
||||
Self(Some(RxAsyncInner { rx, queue }))
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
stop_async_rx(&mut self.0.as_mut().unwrap().rx);
|
||||
}
|
||||
|
||||
pub fn release(mut self) -> (Rx, heapless::spsc::Consumer<'static, u8, N>) {
|
||||
self.stop();
|
||||
let inner = self.0.take().unwrap();
|
||||
(inner.rx, inner.queue)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Drop for RxAsync<N> {
|
||||
fn drop(&mut self) {
|
||||
self.stop();
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> embedded_io_async::Read for RxAsync<N> {
|
||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
let inner = self.0.as_ref().unwrap();
|
||||
// Need to wait for the IRQ to read data and set this flag. If the queue is not
|
||||
// empty, we can read data immediately.
|
||||
if inner.queue.len() == 0 {
|
||||
RX_HAS_DATA[inner.rx.0 as usize].store(false, Ordering::Relaxed);
|
||||
}
|
||||
let _guard = ActiveReadGuard(inner.rx.0 as usize);
|
||||
let mut handle_data_in_queue = |consumer: &mut heapless::spsc::Consumer<'static, u8, N>| {
|
||||
let data_to_read = consumer.len().min(buf.len());
|
||||
for byte in buf.iter_mut().take(data_to_read) {
|
||||
// We own the consumer and we checked that the amount of data is guaranteed to be available.
|
||||
*byte = unsafe { consumer.dequeue_unchecked() };
|
||||
}
|
||||
data_to_read
|
||||
};
|
||||
let mut_ref = self.0.as_mut().unwrap();
|
||||
let fut = RxFuture::new(&mut mut_ref.rx);
|
||||
// Data is available, so read that data immediately.
|
||||
let read_data = handle_data_in_queue(&mut mut_ref.queue);
|
||||
if read_data > 0 {
|
||||
return Ok(read_data);
|
||||
}
|
||||
// Await data.
|
||||
let _ = fut.await;
|
||||
Ok(handle_data_in_queue(&mut mut_ref.queue))
|
||||
}
|
||||
}
|
||||
|
||||
struct RxAsyncOverwritingInner<const N: usize> {
|
||||
rx: Rx,
|
||||
pub shared_consumer: &'static Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
|
||||
}
|
||||
|
||||
/// Core data structure to allow asynchronous UART reception.
|
||||
///
|
||||
/// If the ring buffer becomes full, the oldest data will be overwritten when using the
|
||||
/// [on_interrupt_rx_overwriting] interrupt handlers.
|
||||
pub struct RxAsyncOverwriting<const N: usize>(Option<RxAsyncOverwritingInner<N>>);
|
||||
|
||||
impl<const N: usize> ErrorType for RxAsyncOverwriting<N> {
|
||||
/// Error reporting is done using the result of the interrupt functions.
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
impl<const N: usize> RxAsyncOverwriting<N> {
|
||||
/// Create a new asynchronous receiver.
|
||||
///
|
||||
/// The passed shared [heapless::spsc::Consumer] will be used to asynchronously receive data
|
||||
/// which is filled by the interrupt handler. The shared property allows using it in the
|
||||
/// interrupt handler to overwrite old data.
|
||||
pub fn new(
|
||||
mut rx: Rx,
|
||||
shared_consumer: &'static Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
|
||||
) -> Self {
|
||||
rx.disable_interrupts();
|
||||
rx.disable();
|
||||
rx.clear_fifo();
|
||||
// Enable those together.
|
||||
critical_section::with(|_| {
|
||||
rx.enable_interrupts();
|
||||
rx.enable();
|
||||
});
|
||||
Self(Some(RxAsyncOverwritingInner {
|
||||
rx,
|
||||
shared_consumer,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
stop_async_rx(&mut self.0.as_mut().unwrap().rx);
|
||||
}
|
||||
|
||||
pub fn release(mut self) -> Rx {
|
||||
self.stop();
|
||||
let inner = self.0.take().unwrap();
|
||||
inner.rx
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Drop for RxAsyncOverwriting<N> {
|
||||
fn drop(&mut self) {
|
||||
self.stop();
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> embedded_io_async::Read for RxAsyncOverwriting<N> {
|
||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
let inner = self.0.as_ref().unwrap();
|
||||
let id = inner.rx.0 as usize;
|
||||
// Need to wait for the IRQ to read data and set this flag. If the queue is not
|
||||
// empty, we can read data immediately.
|
||||
|
||||
critical_section::with(|cs| {
|
||||
let queue = inner.shared_consumer.borrow(cs);
|
||||
if queue.borrow().as_ref().unwrap().len() == 0 {
|
||||
RX_HAS_DATA[id].store(false, Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
let _guard = ActiveReadGuard(id);
|
||||
let mut handle_data_in_queue = |inner: &mut RxAsyncOverwritingInner<N>| {
|
||||
critical_section::with(|cs| {
|
||||
let mut consumer_ref = inner.shared_consumer.borrow(cs).borrow_mut();
|
||||
let consumer = consumer_ref.as_mut().unwrap();
|
||||
let data_to_read = consumer.len().min(buf.len());
|
||||
for byte in buf.iter_mut().take(data_to_read) {
|
||||
// We own the consumer and we checked that the amount of data is guaranteed to be available.
|
||||
*byte = unsafe { consumer.dequeue_unchecked() };
|
||||
}
|
||||
data_to_read
|
||||
})
|
||||
};
|
||||
let fut = RxFuture::new(&mut self.0.as_mut().unwrap().rx);
|
||||
// Data is available, so read that data immediately.
|
||||
let read_data = handle_data_in_queue(self.0.as_mut().unwrap());
|
||||
if read_data > 0 {
|
||||
return Ok(read_data);
|
||||
}
|
||||
// Await data.
|
||||
let _ = fut.await;
|
||||
let read_data = handle_data_in_queue(self.0.as_mut().unwrap());
|
||||
Ok(read_data)
|
||||
}
|
||||
}
|
@@ -1,252 +0,0 @@
|
||||
//! # Async UART transmission functionality for the VA108xx family.
|
||||
//!
|
||||
//! This module provides the [TxAsync] struct which implements the [embedded_io_async::Write] trait.
|
||||
//! This trait allows for asynchronous sending of data streams. Please note that this module does
|
||||
//! not specify/declare the interrupt handlers which must be provided for async support to work.
|
||||
//! However, it the [on_interrupt_tx] interrupt handler.
|
||||
//!
|
||||
//! This handler should be called in ALL user interrupt handlers which handle UART TX interrupts
|
||||
//! for a given UART bank.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! - [Async UART TX example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-tx.rs)
|
||||
use core::{cell::RefCell, future::Future};
|
||||
|
||||
use critical_section::Mutex;
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use embedded_io_async::Write;
|
||||
use portable_atomic::AtomicBool;
|
||||
|
||||
use super::*;
|
||||
|
||||
static UART_TX_WAKERS: [AtomicWaker; 2] = [const { AtomicWaker::new() }; 2];
|
||||
static TX_CONTEXTS: [Mutex<RefCell<TxContext>>; 2] =
|
||||
[const { Mutex::new(RefCell::new(TxContext::new())) }; 2];
|
||||
// Completion flag. Kept outside of the context structure as an atomic to avoid
|
||||
// critical section.
|
||||
static TX_DONE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
|
||||
|
||||
/// This is a generic interrupt handler to handle asynchronous UART TX operations for a given
|
||||
/// UART bank.
|
||||
///
|
||||
/// The user has to call this once in the interrupt handler responsible for the TX interrupts on
|
||||
/// the given UART bank.
|
||||
pub fn on_interrupt_tx(bank: UartId) {
|
||||
let uart = unsafe { bank.reg_block() };
|
||||
let idx = bank as usize;
|
||||
let irq_enb = uart.irq_enb().read();
|
||||
// IRQ is not related to TX.
|
||||
if irq_enb.irq_tx().bit_is_clear() || irq_enb.irq_tx_empty().bit_is_clear() {
|
||||
return;
|
||||
}
|
||||
|
||||
let tx_status = uart.txstatus().read();
|
||||
let unexpected_overrun = tx_status.wrlost().bit_is_set();
|
||||
let mut context = critical_section::with(|cs| {
|
||||
let context_ref = TX_CONTEXTS[idx].borrow(cs);
|
||||
*context_ref.borrow()
|
||||
});
|
||||
context.tx_overrun = unexpected_overrun;
|
||||
if context.progress >= context.slice.len && !tx_status.wrbusy().bit_is_set() {
|
||||
uart.irq_enb().modify(|_, w| {
|
||||
w.irq_tx().clear_bit();
|
||||
w.irq_tx_empty().clear_bit();
|
||||
w.irq_tx_status().clear_bit()
|
||||
});
|
||||
uart.enable().modify(|_, w| w.txenable().clear_bit());
|
||||
// Write back updated context structure.
|
||||
critical_section::with(|cs| {
|
||||
let context_ref = TX_CONTEXTS[idx].borrow(cs);
|
||||
*context_ref.borrow_mut() = context;
|
||||
});
|
||||
// Transfer is done.
|
||||
TX_DONE[idx].store(true, core::sync::atomic::Ordering::Relaxed);
|
||||
UART_TX_WAKERS[idx].wake();
|
||||
return;
|
||||
}
|
||||
// Safety: We documented that the user provided slice must outlive the future, so we convert
|
||||
// the raw pointer back to the slice here.
|
||||
let slice = unsafe { core::slice::from_raw_parts(context.slice.data, context.slice.len) };
|
||||
while context.progress < context.slice.len {
|
||||
let wrrdy = uart.txstatus().read().wrrdy().bit_is_set();
|
||||
if !wrrdy {
|
||||
break;
|
||||
}
|
||||
// Safety: TX structure is owned by the future which does not write into the the data
|
||||
// register, so we can assume we are the only one writing to the data register.
|
||||
uart.data()
|
||||
.write(|w| unsafe { w.bits(slice[context.progress] as u32) });
|
||||
context.progress += 1;
|
||||
}
|
||||
|
||||
// Write back updated context structure.
|
||||
critical_section::with(|cs| {
|
||||
let context_ref = TX_CONTEXTS[idx].borrow(cs);
|
||||
*context_ref.borrow_mut() = context;
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TxContext {
|
||||
progress: usize,
|
||||
tx_overrun: bool,
|
||||
slice: RawBufSlice,
|
||||
}
|
||||
|
||||
#[allow(clippy::new_without_default)]
|
||||
impl TxContext {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
progress: 0,
|
||||
tx_overrun: false,
|
||||
slice: RawBufSlice::new_empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct RawBufSlice {
|
||||
data: *const u8,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
/// Safety: This type MUST be used with mutex to ensure concurrent access is valid.
|
||||
unsafe impl Send for RawBufSlice {}
|
||||
|
||||
impl RawBufSlice {
|
||||
/// # Safety
|
||||
///
|
||||
/// This function stores the raw pointer of the passed data slice. The user MUST ensure
|
||||
/// that the slice outlives the data structure.
|
||||
#[allow(dead_code)]
|
||||
const unsafe fn new(data: &[u8]) -> Self {
|
||||
Self {
|
||||
data: data.as_ptr(),
|
||||
len: data.len(),
|
||||
}
|
||||
}
|
||||
|
||||
const fn new_empty() -> Self {
|
||||
Self {
|
||||
data: core::ptr::null(),
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This function stores the raw pointer of the passed data slice. The user MUST ensure
|
||||
/// that the slice outlives the data structure.
|
||||
pub unsafe fn set(&mut self, data: &[u8]) {
|
||||
self.data = data.as_ptr();
|
||||
self.len = data.len();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TxFuture {
|
||||
uart_idx: usize,
|
||||
}
|
||||
|
||||
impl TxFuture {
|
||||
/// # Safety
|
||||
///
|
||||
/// This function stores the raw pointer of the passed data slice. The user MUST ensure
|
||||
/// that the slice outlives the data structure.
|
||||
pub unsafe fn new(tx: &mut Tx, data: &[u8]) -> Self {
|
||||
TX_DONE[tx.0 as usize].store(false, core::sync::atomic::Ordering::Relaxed);
|
||||
tx.disable_interrupts();
|
||||
tx.disable();
|
||||
tx.clear_fifo();
|
||||
|
||||
let uart_tx = tx.regs_priv();
|
||||
let init_fill_count = core::cmp::min(data.len(), 16);
|
||||
// We fill the FIFO.
|
||||
for data in data.iter().take(init_fill_count) {
|
||||
uart_tx.data().write(|w| unsafe { w.bits(*data as u32) });
|
||||
}
|
||||
critical_section::with(|cs| {
|
||||
let context_ref = TX_CONTEXTS[tx.0 as usize].borrow(cs);
|
||||
let mut context = context_ref.borrow_mut();
|
||||
context.slice.set(data);
|
||||
context.progress = init_fill_count;
|
||||
|
||||
// Ensure those are enabled inside a critical section at the same time. Can lead to
|
||||
// weird glitches otherwise.
|
||||
tx.enable_interrupts();
|
||||
tx.enable();
|
||||
});
|
||||
Self {
|
||||
uart_idx: tx.0 as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for TxFuture {
|
||||
type Output = Result<usize, TxOverrunError>;
|
||||
|
||||
fn poll(
|
||||
self: core::pin::Pin<&mut Self>,
|
||||
cx: &mut core::task::Context<'_>,
|
||||
) -> core::task::Poll<Self::Output> {
|
||||
UART_TX_WAKERS[self.uart_idx].register(cx.waker());
|
||||
if TX_DONE[self.uart_idx].swap(false, core::sync::atomic::Ordering::Relaxed) {
|
||||
let progress = critical_section::with(|cs| {
|
||||
TX_CONTEXTS[self.uart_idx].borrow(cs).borrow().progress
|
||||
});
|
||||
return core::task::Poll::Ready(Ok(progress));
|
||||
}
|
||||
core::task::Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TxFuture {
|
||||
fn drop(&mut self) {
|
||||
let reg_block = match self.uart_idx {
|
||||
0 => unsafe { pac::Uarta::reg_block() },
|
||||
1 => unsafe { pac::Uartb::reg_block() },
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
disable_tx_interrupts(reg_block);
|
||||
disable_tx(reg_block);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TxAsync(Tx);
|
||||
|
||||
impl TxAsync {
|
||||
pub fn new(tx: Tx) -> Self {
|
||||
Self(tx)
|
||||
}
|
||||
|
||||
pub fn release(self) -> Tx {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[error("TX overrun error")]
|
||||
pub struct TxOverrunError;
|
||||
|
||||
impl embedded_io_async::Error for TxOverrunError {
|
||||
fn kind(&self) -> embedded_io_async::ErrorKind {
|
||||
embedded_io_async::ErrorKind::Other
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_io::ErrorType for TxAsync {
|
||||
type Error = TxOverrunError;
|
||||
}
|
||||
|
||||
impl Write for TxAsync {
|
||||
/// Write a buffer asynchronously.
|
||||
///
|
||||
/// This implementation is not side effect free, and a started future might have already
|
||||
/// written part of the passed buffer.
|
||||
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
let fut = unsafe { TxFuture::new(&mut self.0, buf) };
|
||||
fut.await
|
||||
}
|
||||
}
|
@@ -33,7 +33,8 @@ portable-atomic = { version = "1", features = ["unsafe-assume-single-core"] }
|
||||
portable-atomic = "1"
|
||||
|
||||
[features]
|
||||
vor1x = ["_family-selected", "va108xx"]
|
||||
vor1x = ["_family-selected", "dep:va108xx"]
|
||||
vor4x = ["_family-selected"]
|
||||
defmt = ["dep:defmt", "arbitrary-int/defmt"]
|
||||
|
||||
_family-selected = []
|
||||
|
665
vorago-shared-periphs/src/i2c/mod.rs
Normal file
665
vorago-shared-periphs/src/i2c/mod.rs
Normal file
@@ -0,0 +1,665 @@
|
||||
pub mod regs;
|
||||
|
||||
use crate::{
|
||||
enable_peripheral_clock, sealed::Sealed, sysconfig::reset_peripheral_for_cycles, time::Hertz,
|
||||
PeripheralSelect,
|
||||
};
|
||||
use arbitrary_int::{u10, u11, u20, u4};
|
||||
use core::marker::PhantomData;
|
||||
use embedded_hal::i2c::{self, Operation, SevenBitAddress, TenBitAddress};
|
||||
use regs::ClkTimeoutLimit;
|
||||
pub use regs::{Bank, I2cSpeed, RxFifoFullMode, TxFifoEmptyMode};
|
||||
|
||||
#[cfg(feature = "vor1x")]
|
||||
use va108xx as pac;
|
||||
|
||||
//==================================================================================================
|
||||
// Defintions
|
||||
//==================================================================================================
|
||||
|
||||
const CLK_100K: Hertz = Hertz::from_raw(100_000);
|
||||
const CLK_400K: Hertz = Hertz::from_raw(400_000);
|
||||
const MIN_CLK_400K: Hertz = Hertz::from_raw(8_000_000);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[error("clock too slow for fast I2C mode")]
|
||||
pub struct ClockTooSlowForFastI2cError;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||
#[error("invalid timing parameters")]
|
||||
pub struct InvalidTimingParamsError;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Error {
|
||||
#[error("arbitration lost")]
|
||||
ArbitrationLost,
|
||||
#[error("nack address")]
|
||||
NackAddr,
|
||||
/// Data not acknowledged in write operation
|
||||
#[error("data not acknowledged in write operation")]
|
||||
NackData,
|
||||
/// Not enough data received in read operation
|
||||
#[error("insufficient data received")]
|
||||
InsufficientDataReceived,
|
||||
/// Number of bytes in transfer too large (larger than 0x7fe)
|
||||
#[error("data too large (larger than 0x7fe)")]
|
||||
DataTooLarge,
|
||||
#[error("clock timeout, SCL was low for {0} clock cycles")]
|
||||
ClockTimeout(u20),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum InitError {
|
||||
/// Wrong address used in constructor
|
||||
#[error("wrong address mode")]
|
||||
WrongAddrMode,
|
||||
/// APB1 clock is too slow for fast I2C mode.
|
||||
#[error("clock too slow for fast I2C mode: {0}")]
|
||||
ClkTooSlow(#[from] ClockTooSlowForFastI2cError),
|
||||
}
|
||||
|
||||
impl embedded_hal::i2c::Error for Error {
|
||||
fn kind(&self) -> embedded_hal::i2c::ErrorKind {
|
||||
match self {
|
||||
Error::ArbitrationLost => embedded_hal::i2c::ErrorKind::ArbitrationLoss,
|
||||
Error::NackAddr => {
|
||||
embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Address)
|
||||
}
|
||||
Error::NackData => {
|
||||
embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Data)
|
||||
}
|
||||
Error::DataTooLarge | Error::InsufficientDataReceived | Error::ClockTimeout(_) => {
|
||||
embedded_hal::i2c::ErrorKind::Other
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum I2cCmd {
|
||||
Start = 0b01,
|
||||
Stop = 0b10,
|
||||
StartWithStop = 0b11,
|
||||
Cancel = 0b100,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum I2cAddress {
|
||||
Regular(u8),
|
||||
TenBit(u16),
|
||||
}
|
||||
|
||||
impl I2cAddress {
|
||||
pub fn ten_bit_addr(&self) -> bool {
|
||||
match self {
|
||||
I2cAddress::Regular(_) => false,
|
||||
I2cAddress::TenBit(_) => true,
|
||||
}
|
||||
}
|
||||
pub fn raw(&self) -> u16 {
|
||||
match self {
|
||||
I2cAddress::Regular(addr) => *addr as u16,
|
||||
I2cAddress::TenBit(addr) => *addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type I2cRegBlock = pac::i2ca::RegisterBlock;
|
||||
|
||||
/// Common trait implemented by all PAC peripheral access structures. The register block
|
||||
/// format is the same for all SPI blocks.
|
||||
pub trait I2cMarker: Sealed {
|
||||
const ID: Bank;
|
||||
const PERIPH_SEL: PeripheralSelect;
|
||||
}
|
||||
|
||||
impl I2cMarker for pac::I2ca {
|
||||
const ID: Bank = Bank::I2c0;
|
||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c0;
|
||||
}
|
||||
impl Sealed for pac::I2ca {}
|
||||
|
||||
impl I2cMarker for pac::I2cb {
|
||||
const ID: Bank = Bank::I2c1;
|
||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c1;
|
||||
}
|
||||
impl Sealed for pac::I2cb {}
|
||||
|
||||
//==================================================================================================
|
||||
// Config
|
||||
//==================================================================================================
|
||||
|
||||
fn calc_clk_div(sys_clk: Hertz, speed_mode: I2cSpeed) -> Result<u8, ClockTooSlowForFastI2cError> {
|
||||
if speed_mode == I2cSpeed::Regular100khz {
|
||||
Ok(((sys_clk.raw() / CLK_100K.raw() / 20) - 1) as u8)
|
||||
} else {
|
||||
if sys_clk.raw() < MIN_CLK_400K.raw() {
|
||||
return Err(ClockTooSlowForFastI2cError);
|
||||
}
|
||||
Ok(((sys_clk.raw() / CLK_400K.raw() / 25) - 1) as u8)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct TimingConfig {
|
||||
pub t_rise: u4,
|
||||
pub t_fall: u4,
|
||||
pub t_high: u4,
|
||||
pub t_low: u4,
|
||||
pub tsu_stop: u4,
|
||||
pub tsu_start: u4,
|
||||
pub thd_start: u4,
|
||||
pub t_buf: u4,
|
||||
}
|
||||
|
||||
/// Default configuration are the register reset value which are used by default.
|
||||
impl Default for TimingConfig {
|
||||
fn default() -> Self {
|
||||
TimingConfig {
|
||||
t_rise: u4::new(0b0010),
|
||||
t_fall: u4::new(0b0001),
|
||||
t_high: u4::new(0b1000),
|
||||
t_low: u4::new(0b1001),
|
||||
tsu_stop: u4::new(0b1000),
|
||||
tsu_start: u4::new(0b1010),
|
||||
thd_start: u4::new(0b1000),
|
||||
t_buf: u4::new(0b1010),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct MasterConfig {
|
||||
pub tx_empty_mode: TxFifoEmptyMode,
|
||||
pub rx_full_mode: RxFifoFullMode,
|
||||
/// Enable the analog delay glitch filter
|
||||
pub alg_filt: bool,
|
||||
/// Enable the digital glitch filter
|
||||
pub dlg_filt: bool,
|
||||
pub timing_config: Option<TimingConfig>,
|
||||
/// See [I2cMaster::set_clock_low_timeout] documentation.
|
||||
pub timeout: Option<u20>,
|
||||
// Loopback mode
|
||||
// lbm: bool,
|
||||
}
|
||||
|
||||
impl Default for MasterConfig {
|
||||
fn default() -> Self {
|
||||
MasterConfig {
|
||||
tx_empty_mode: TxFifoEmptyMode::Stall,
|
||||
rx_full_mode: RxFifoFullMode::Stall,
|
||||
alg_filt: false,
|
||||
dlg_filt: false,
|
||||
timeout: None,
|
||||
timing_config: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sealed for MasterConfig {}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum WriteCompletionCondition {
|
||||
Idle,
|
||||
Waiting,
|
||||
}
|
||||
|
||||
struct TimeoutGuard {
|
||||
clk_timeout_enabled: bool,
|
||||
regs: regs::MmioI2c<'static>,
|
||||
}
|
||||
|
||||
impl TimeoutGuard {
|
||||
fn new(regs: ®s::MmioI2c<'static>) -> Self {
|
||||
let clk_timeout_enabled = regs.read_clk_timeout_limit().value().value() > 0;
|
||||
let mut guard = TimeoutGuard {
|
||||
clk_timeout_enabled,
|
||||
regs: unsafe { regs.clone() },
|
||||
};
|
||||
if clk_timeout_enabled {
|
||||
// Clear any interrupts which might be pending.
|
||||
guard.regs.write_irq_clear(
|
||||
regs::InterruptClear::builder()
|
||||
.with_clock_timeout(true)
|
||||
.with_tx_overflow(false)
|
||||
.with_rx_overflow(false)
|
||||
.build(),
|
||||
);
|
||||
guard.regs.modify_irq_enb(|mut value| {
|
||||
value.set_clock_timeout(true);
|
||||
value
|
||||
});
|
||||
}
|
||||
guard
|
||||
}
|
||||
|
||||
fn timeout_enabled(&self) -> bool {
|
||||
self.clk_timeout_enabled
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TimeoutGuard {
|
||||
fn drop(&mut self) {
|
||||
if self.clk_timeout_enabled {
|
||||
self.regs.modify_irq_enb(|mut value| {
|
||||
value.set_clock_timeout(false);
|
||||
value
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
//==================================================================================================
|
||||
// I2C Master
|
||||
//==================================================================================================
|
||||
|
||||
pub struct I2cMaster<Addr = SevenBitAddress> {
|
||||
id: Bank,
|
||||
regs: regs::MmioI2c<'static>,
|
||||
addr: PhantomData<Addr>,
|
||||
}
|
||||
|
||||
impl<Addr> I2cMaster<Addr> {
|
||||
pub fn new<I2c: I2cMarker>(
|
||||
sysclk: Hertz,
|
||||
_i2c: I2c,
|
||||
cfg: MasterConfig,
|
||||
speed_mode: I2cSpeed,
|
||||
) -> Result<Self, ClockTooSlowForFastI2cError> {
|
||||
reset_peripheral_for_cycles(I2c::PERIPH_SEL, 2);
|
||||
enable_peripheral_clock(I2c::PERIPH_SEL);
|
||||
let mut regs = regs::I2c::new_mmio(I2c::ID);
|
||||
|
||||
let clk_div = calc_clk_div(sysclk, speed_mode)?;
|
||||
regs.write_clkscale(
|
||||
regs::ClkScale::builder()
|
||||
.with_div(clk_div)
|
||||
.with_fastmode(speed_mode)
|
||||
.build(),
|
||||
);
|
||||
regs.modify_control(|mut value| {
|
||||
value.set_tx_fifo_empty_mode(cfg.tx_empty_mode);
|
||||
value.set_rx_fifo_full_mode(cfg.rx_full_mode);
|
||||
value.set_analog_filter(cfg.alg_filt);
|
||||
value.set_digital_filter(cfg.dlg_filt);
|
||||
value
|
||||
});
|
||||
|
||||
if let Some(ref timing_cfg) = cfg.timing_config {
|
||||
regs.modify_control(|mut value| {
|
||||
value.set_enable_timing_config(true);
|
||||
value
|
||||
});
|
||||
regs.write_timing_config(
|
||||
regs::TimingConfig::builder()
|
||||
.with_t_rise(timing_cfg.t_rise)
|
||||
.with_t_fall(timing_cfg.t_fall)
|
||||
.with_t_high(timing_cfg.t_high)
|
||||
.with_t_low(timing_cfg.t_low)
|
||||
.with_tsu_stop(timing_cfg.tsu_stop)
|
||||
.with_tsu_start(timing_cfg.tsu_start)
|
||||
.with_thd_start(timing_cfg.thd_start)
|
||||
.with_t_buf(timing_cfg.t_buf)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
regs.write_fifo_clear(
|
||||
regs::FifoClear::builder()
|
||||
.with_tx_fifo(true)
|
||||
.with_rx_fifo(true)
|
||||
.build(),
|
||||
);
|
||||
if let Some(timeout) = cfg.timeout {
|
||||
regs.write_clk_timeout_limit(ClkTimeoutLimit::new(timeout));
|
||||
}
|
||||
let mut i2c_master = I2cMaster {
|
||||
addr: PhantomData,
|
||||
id: I2c::ID,
|
||||
regs,
|
||||
};
|
||||
i2c_master.enable();
|
||||
Ok(i2c_master)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Bank {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Configures the clock scale for a given speed mode setting
|
||||
pub fn set_clk_scale(
|
||||
&mut self,
|
||||
sys_clk: Hertz,
|
||||
speed_mode: I2cSpeed,
|
||||
) -> Result<(), ClockTooSlowForFastI2cError> {
|
||||
self.disable();
|
||||
let clk_div = calc_clk_div(sys_clk, speed_mode)?;
|
||||
self.regs.write_clkscale(
|
||||
regs::ClkScale::builder()
|
||||
.with_div(clk_div)
|
||||
.with_fastmode(speed_mode)
|
||||
.build(),
|
||||
);
|
||||
self.enable();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cancel_transfer(&mut self) {
|
||||
self.regs.write_cmd(
|
||||
regs::Command::builder()
|
||||
.with_start(false)
|
||||
.with_stop(false)
|
||||
.with_cancel(true)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clear_tx_fifo(&mut self) {
|
||||
self.regs.write_fifo_clear(
|
||||
regs::FifoClear::builder()
|
||||
.with_tx_fifo(true)
|
||||
.with_rx_fifo(false)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clear_rx_fifo(&mut self) {
|
||||
self.regs.write_fifo_clear(
|
||||
regs::FifoClear::builder()
|
||||
.with_tx_fifo(false)
|
||||
.with_rx_fifo(true)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Configure a timeout limit on the amount of time the I2C clock is seen to be low.
|
||||
/// The timeout is specified as I2C clock cycles.
|
||||
///
|
||||
/// If the timeout is enabled, the blocking transaction handlers provided by the [I2cMaster]
|
||||
/// will poll the interrupt status register to check for timeouts. This can be used to avoid
|
||||
/// hang-ups of the I2C bus.
|
||||
#[inline]
|
||||
pub fn set_clock_low_timeout(&mut self, clock_cycles: u20) {
|
||||
self.regs.write_clk_timeout_limit(ClkTimeoutLimit::new(clock_cycles));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn disable_clock_low_timeout(&mut self) {
|
||||
self.regs.write_clk_timeout_limit(ClkTimeoutLimit::new(u20::new(0)));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn enable(&mut self) {
|
||||
self.regs.modify_control(|mut value| {
|
||||
value.set_enable(true);
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn disable(&mut self) {
|
||||
self.regs.modify_control(|mut value| {
|
||||
value.set_enable(false);
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn write_fifo_unchecked(&mut self, word: u8) {
|
||||
self.regs.write_data(regs::Data::new(word));
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn read_fifo_unchecked(&self) -> u8 {
|
||||
self.regs.read_data().data()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_status(&mut self) -> regs::Status {
|
||||
self.regs.read_status()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_command(&mut self, cmd: I2cCmd) {
|
||||
self.regs
|
||||
.write_cmd(regs::Command::new_with_raw_value(cmd as u32));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_address(&mut self, addr: I2cAddress, dir: regs::Direction) {
|
||||
self.regs.write_address(
|
||||
regs::Address::builder()
|
||||
.with_direction(dir)
|
||||
.with_address(u10::new(addr.raw()))
|
||||
.with_a10_mode(addr.ten_bit_addr())
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
fn error_handler_write(&mut self, init_cmd: I2cCmd) {
|
||||
if init_cmd == I2cCmd::Start {
|
||||
self.write_command(I2cCmd::Stop);
|
||||
}
|
||||
// The other case is start with stop where, so a CANCEL command should not be necessary
|
||||
// because the hardware takes care of it.
|
||||
self.clear_tx_fifo();
|
||||
}
|
||||
|
||||
/// Blocking write transaction on the I2C bus.
|
||||
pub fn write_blocking(&mut self, addr: I2cAddress, output: &[u8]) -> Result<(), Error> {
|
||||
self.write_blocking_generic(
|
||||
I2cCmd::StartWithStop,
|
||||
addr,
|
||||
output,
|
||||
WriteCompletionCondition::Idle,
|
||||
)
|
||||
}
|
||||
|
||||
/// Blocking read transaction on the I2C bus.
|
||||
pub fn read_blocking(&mut self, addr: I2cAddress, buffer: &mut [u8]) -> Result<(), Error> {
|
||||
let len = buffer.len();
|
||||
if len > 0x7fe {
|
||||
return Err(Error::DataTooLarge);
|
||||
}
|
||||
// Clear the receive FIFO
|
||||
self.clear_rx_fifo();
|
||||
|
||||
let timeout_guard = TimeoutGuard::new(&self.regs);
|
||||
|
||||
// Load number of words
|
||||
self.regs
|
||||
.write_words(regs::Words::new(u11::new(len as u16)));
|
||||
// Load address
|
||||
self.write_address(addr, regs::Direction::Receive);
|
||||
|
||||
let mut buf_iter = buffer.iter_mut();
|
||||
let mut read_bytes = 0;
|
||||
// Start receive transfer
|
||||
self.write_command(I2cCmd::StartWithStop);
|
||||
loop {
|
||||
let status = self.read_status();
|
||||
if status.arb_lost() {
|
||||
self.clear_rx_fifo();
|
||||
return Err(Error::ArbitrationLost);
|
||||
}
|
||||
if status.nack_addr() {
|
||||
self.clear_rx_fifo();
|
||||
return Err(Error::NackAddr);
|
||||
}
|
||||
if status.idle() {
|
||||
if read_bytes != len {
|
||||
return Err(Error::InsufficientDataReceived);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
if timeout_guard.timeout_enabled() && self.regs.read_irq_status().clock_timeout() {
|
||||
return Err(Error::ClockTimeout(
|
||||
self.regs.read_clk_timeout_limit().value(),
|
||||
));
|
||||
}
|
||||
if status.rx_not_empty() {
|
||||
if let Some(next_byte) = buf_iter.next() {
|
||||
*next_byte = self.read_fifo_unchecked();
|
||||
}
|
||||
read_bytes += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_blocking_generic(
|
||||
&mut self,
|
||||
init_cmd: I2cCmd,
|
||||
addr: I2cAddress,
|
||||
output: &[u8],
|
||||
end_condition: WriteCompletionCondition,
|
||||
) -> Result<(), Error> {
|
||||
let len = output.len();
|
||||
if len > 0x7fe {
|
||||
return Err(Error::DataTooLarge);
|
||||
}
|
||||
// Clear the send FIFO
|
||||
self.clear_tx_fifo();
|
||||
|
||||
let timeout_guard = TimeoutGuard::new(&self.regs);
|
||||
|
||||
// Load number of words
|
||||
self.regs
|
||||
.write_words(regs::Words::new(u11::new(len as u16)));
|
||||
let mut bytes = output.iter();
|
||||
// FIFO has a depth of 16. We load slightly above the trigger level
|
||||
// but not all of it because the transaction might fail immediately
|
||||
const FILL_DEPTH: usize = 12;
|
||||
|
||||
let mut current_index = core::cmp::min(FILL_DEPTH, len);
|
||||
// load the FIFO
|
||||
for _ in 0..current_index {
|
||||
self.write_fifo_unchecked(*bytes.next().unwrap());
|
||||
}
|
||||
self.write_address(addr, regs::Direction::Send);
|
||||
self.write_command(init_cmd);
|
||||
loop {
|
||||
let status = self.regs.read_status();
|
||||
if status.arb_lost() {
|
||||
self.error_handler_write(init_cmd);
|
||||
return Err(Error::ArbitrationLost);
|
||||
}
|
||||
if status.nack_addr() {
|
||||
self.error_handler_write(init_cmd);
|
||||
return Err(Error::NackAddr);
|
||||
}
|
||||
if status.nack_data() {
|
||||
self.error_handler_write(init_cmd);
|
||||
return Err(Error::NackData);
|
||||
}
|
||||
match end_condition {
|
||||
WriteCompletionCondition::Idle => {
|
||||
if status.idle() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
WriteCompletionCondition::Waiting => {
|
||||
if status.waiting() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
if timeout_guard.timeout_enabled() && self.regs.read_irq_status().clock_timeout() {
|
||||
return Err(Error::ClockTimeout(
|
||||
self.regs.read_clk_timeout_limit().value(),
|
||||
));
|
||||
}
|
||||
if status.tx_not_full() && current_index < len {
|
||||
self.write_fifo_unchecked(output[current_index]);
|
||||
current_index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Blocking write-read transaction on the I2C bus.
|
||||
pub fn write_read_blocking(
|
||||
&mut self,
|
||||
address: I2cAddress,
|
||||
write: &[u8],
|
||||
read: &mut [u8],
|
||||
) -> Result<(), Error> {
|
||||
self.write_blocking_generic(
|
||||
I2cCmd::Start,
|
||||
address,
|
||||
write,
|
||||
WriteCompletionCondition::Waiting,
|
||||
)?;
|
||||
self.read_blocking(address, read)
|
||||
}
|
||||
}
|
||||
|
||||
//======================================================================================
|
||||
// Embedded HAL I2C implementations
|
||||
//======================================================================================
|
||||
|
||||
impl embedded_hal::i2c::ErrorType for I2cMaster<SevenBitAddress> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl embedded_hal::i2c::I2c for I2cMaster<SevenBitAddress> {
|
||||
fn transaction(
|
||||
&mut self,
|
||||
address: SevenBitAddress,
|
||||
operations: &mut [Operation<'_>],
|
||||
) -> Result<(), Self::Error> {
|
||||
for operation in operations {
|
||||
match operation {
|
||||
Operation::Read(buf) => self.read_blocking(I2cAddress::Regular(address), buf)?,
|
||||
Operation::Write(buf) => self.write_blocking(I2cAddress::Regular(address), buf)?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_read(
|
||||
&mut self,
|
||||
address: u8,
|
||||
write: &[u8],
|
||||
read: &mut [u8],
|
||||
) -> Result<(), Self::Error> {
|
||||
let addr = I2cAddress::Regular(address);
|
||||
self.write_read_blocking(addr, write, read)
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal::i2c::ErrorType for I2cMaster<TenBitAddress> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl embedded_hal::i2c::I2c<TenBitAddress> for I2cMaster<TenBitAddress> {
|
||||
fn transaction(
|
||||
&mut self,
|
||||
address: TenBitAddress,
|
||||
operations: &mut [Operation<'_>],
|
||||
) -> Result<(), Self::Error> {
|
||||
for operation in operations {
|
||||
match operation {
|
||||
Operation::Read(buf) => self.read_blocking(I2cAddress::TenBit(address), buf)?,
|
||||
Operation::Write(buf) => self.write_blocking(I2cAddress::TenBit(address), buf)?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_read(
|
||||
&mut self,
|
||||
address: TenBitAddress,
|
||||
write: &[u8],
|
||||
read: &mut [u8],
|
||||
) -> Result<(), Self::Error> {
|
||||
let addr = I2cAddress::TenBit(address);
|
||||
self.write_read_blocking(addr, write, read)
|
||||
}
|
||||
}
|
671
vorago-shared-periphs/src/i2c/regs.rs
Normal file
671
vorago-shared-periphs/src/i2c/regs.rs
Normal file
@@ -0,0 +1,671 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use arbitrary_int::{u10, u11, u20, u4, u5, u9};
|
||||
|
||||
pub use crate::shared::{FifoClear, TriggerLevel};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "vor1x")] {
|
||||
/// I2C A base address
|
||||
pub const BASE_ADDR_0: usize = 0x4006_0000;
|
||||
/// I2C B base address
|
||||
pub const BASE_ADDR_1: usize = 0x4006_1000;
|
||||
} else if #[cfg(feature = "vor4x")] {
|
||||
/// I2C 0 base address
|
||||
pub const BASE_ADDR_0: usize = 0x4001_6000;
|
||||
/// I2C 1 base address
|
||||
pub const BASE_ADDR_1: usize = 0x4001_6400;
|
||||
/// I2C 2 base address
|
||||
pub const BASE_ADDR_2: usize = 0x4001_6800;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Bank {
|
||||
I2c0 = 0,
|
||||
I2c1 = 1,
|
||||
#[cfg(feature = "vor4x")]
|
||||
I2c2 = 2,
|
||||
}
|
||||
|
||||
impl Bank {
|
||||
/// Unsafely steal the I2C peripheral block for the given port.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Circumvents ownership and safety guarantees by the HAL.
|
||||
pub unsafe fn steal_regs(&self) -> MmioI2c<'static> {
|
||||
I2c::new_mmio(*self)
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum TxFifoEmptyMode {
|
||||
/// I2C clock is stretched until data is available.
|
||||
#[default]
|
||||
Stall = 0,
|
||||
EndTransaction = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum RxFifoFullMode {
|
||||
/// I2C clock is stretched until data is available.
|
||||
#[default]
|
||||
Stall = 0,
|
||||
Nack = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct Control {
|
||||
#[bit(0, r)]
|
||||
clk_enabled: bool,
|
||||
#[bit(1, r)]
|
||||
enabled: bool,
|
||||
#[bit(2, rw)]
|
||||
enable: bool,
|
||||
#[bit(3, rw)]
|
||||
tx_fifo_empty_mode: TxFifoEmptyMode,
|
||||
#[bit(4, rw)]
|
||||
rx_fifo_full_mode: RxFifoFullMode,
|
||||
/// Enables the analog delay glitch filter.
|
||||
#[bit(5, rw)]
|
||||
analog_filter: bool,
|
||||
/// Enables the digital glitch filter.
|
||||
#[bit(6, rw)]
|
||||
digital_filter: bool,
|
||||
#[bit(8, rw)]
|
||||
loopback: bool,
|
||||
#[bit(9, rw)]
|
||||
enable_timing_config: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum I2cSpeed {
|
||||
Regular100khz = 0,
|
||||
Fast400khz = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
#[derive(Debug)]
|
||||
pub struct ClkScale {
|
||||
/// Clock divide value. Reset value: 0x18.
|
||||
#[bits(0..=7, rw)]
|
||||
div: u8,
|
||||
#[bit(31, rw)]
|
||||
fastmode: I2cSpeed,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct Words(arbitrary_int::UInt<u32, 11>);
|
||||
|
||||
impl Words {
|
||||
pub const fn new(value: u11) -> Self {
|
||||
Words(arbitrary_int::UInt::<u32, 11>::new(value.value() as u32))
|
||||
}
|
||||
pub const fn value(&self) -> u11 {
|
||||
u11::new(self.0.value() as u16)
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Direction {
|
||||
#[default]
|
||||
Send = 0,
|
||||
Receive = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
#[derive(Debug)]
|
||||
pub struct Address {
|
||||
#[bit(0, rw)]
|
||||
direction: Direction,
|
||||
#[bits(1..=10, rw)]
|
||||
address: u10,
|
||||
/// Enables 10-bit addressing mode.
|
||||
#[bit(15, rw)]
|
||||
a10_mode: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct Data(arbitrary_int::UInt<u32, 8>);
|
||||
|
||||
impl Data {
|
||||
pub const fn new(value: u8) -> Self {
|
||||
Data(arbitrary_int::UInt::<u32, 8>::new(value as u32))
|
||||
}
|
||||
|
||||
pub const fn data(&self) -> u8 {
|
||||
self.0.value() as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
#[derive(Debug)]
|
||||
pub struct Command {
|
||||
#[bit(0, w)]
|
||||
start: bool,
|
||||
#[bit(1, w)]
|
||||
stop: bool,
|
||||
#[bit(2, w)]
|
||||
cancel: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct Status {
|
||||
#[bit(0, r)]
|
||||
i2c_idle: bool,
|
||||
#[bit(1, r)]
|
||||
idle: bool,
|
||||
#[bit(2, r)]
|
||||
waiting: bool,
|
||||
#[bit(3, r)]
|
||||
stalled: bool,
|
||||
#[bit(4, r)]
|
||||
arb_lost: bool,
|
||||
#[bit(5, r)]
|
||||
nack_addr: bool,
|
||||
#[bit(6, r)]
|
||||
nack_data: bool,
|
||||
#[bit(8, r)]
|
||||
rx_not_empty: bool,
|
||||
#[bit(9, r)]
|
||||
rx_full: bool,
|
||||
#[bit(11, r)]
|
||||
rx_trigger: bool,
|
||||
#[bit(12, r)]
|
||||
tx_empty: bool,
|
||||
#[bit(13, r)]
|
||||
tx_not_full: bool,
|
||||
#[bit(15, r)]
|
||||
tx_trigger: bool,
|
||||
#[bit(30, r)]
|
||||
raw_sda: bool,
|
||||
#[bit(31, r)]
|
||||
raw_scl: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct State {
|
||||
#[bits(0..=3, rw)]
|
||||
state: u4,
|
||||
#[bits(4..=7, rw)]
|
||||
step: u4,
|
||||
#[bits(8..=12, rw)]
|
||||
rx_fifo: u5,
|
||||
#[bits(14..=18, rw)]
|
||||
tx_fifo: u5,
|
||||
#[bits(20..=28, rw)]
|
||||
bitstate: u9,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct DataCount(arbitrary_int::UInt<u32, 11>);
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct InterruptControl {
|
||||
#[bit(0, rw)]
|
||||
i2c_idle: bool,
|
||||
#[bit(1, rw)]
|
||||
idle: bool,
|
||||
#[bit(2, rw)]
|
||||
waiting: bool,
|
||||
#[bit(3, rw)]
|
||||
stalled: bool,
|
||||
#[bit(4, rw)]
|
||||
arb_lost: bool,
|
||||
#[bit(5, rw)]
|
||||
nack_addr: bool,
|
||||
#[bit(6, rw)]
|
||||
nack_data: bool,
|
||||
#[bit(7, rw)]
|
||||
clock_timeout: bool,
|
||||
#[bit(10, rw)]
|
||||
tx_overflow: bool,
|
||||
#[bit(11, rw)]
|
||||
rx_overflow: bool,
|
||||
#[bit(12, rw)]
|
||||
tx_ready: bool,
|
||||
#[bit(13, rw)]
|
||||
rx_ready: bool,
|
||||
#[bit(14, rw)]
|
||||
tx_empty: bool,
|
||||
#[bit(15, rw)]
|
||||
rx_full: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct InterruptStatus {
|
||||
#[bit(0, r)]
|
||||
i2c_idle: bool,
|
||||
#[bit(1, r)]
|
||||
idle: bool,
|
||||
#[bit(2, r)]
|
||||
waiting: bool,
|
||||
#[bit(3, r)]
|
||||
stalled: bool,
|
||||
#[bit(4, r)]
|
||||
arb_lost: bool,
|
||||
#[bit(5, r)]
|
||||
nack_addr: bool,
|
||||
#[bit(6, r)]
|
||||
nack_data: bool,
|
||||
#[bit(7, r)]
|
||||
clock_timeout: bool,
|
||||
#[bit(10, r)]
|
||||
tx_overflow: bool,
|
||||
#[bit(11, r)]
|
||||
rx_overflow: bool,
|
||||
#[bit(12, r)]
|
||||
tx_ready: bool,
|
||||
#[bit(13, r)]
|
||||
rx_ready: bool,
|
||||
#[bit(14, r)]
|
||||
tx_empty: bool,
|
||||
#[bit(15, r)]
|
||||
rx_full: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
#[derive(Debug)]
|
||||
pub struct InterruptClear {
|
||||
#[bit(7, w)]
|
||||
clock_timeout: bool,
|
||||
#[bit(10, w)]
|
||||
tx_overflow: bool,
|
||||
#[bit(11, w)]
|
||||
rx_overflow: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct TimingConfig {
|
||||
/// Rise time.
|
||||
#[bits(0..=3, rw)]
|
||||
t_rise: u4,
|
||||
/// Fall time.
|
||||
#[bits(4..=7, rw)]
|
||||
t_fall: u4,
|
||||
/// Duty cycle high time of SCL.
|
||||
#[bits(8..=11, rw)]
|
||||
t_high: u4,
|
||||
/// Duty cycle low time of SCL.
|
||||
#[bits(12..=15, rw)]
|
||||
t_low: u4,
|
||||
/// Setup time for STOP.
|
||||
#[bits(16..=19, rw)]
|
||||
tsu_stop: u4,
|
||||
/// Setup time for START.
|
||||
#[bits(20..=23, rw)]
|
||||
tsu_start: u4,
|
||||
/// Data hold time.
|
||||
#[bits(24..=27, rw)]
|
||||
thd_start: u4,
|
||||
/// TBus free time between STOP and START.
|
||||
#[bits(28..=31, rw)]
|
||||
t_buf: u4,
|
||||
}
|
||||
|
||||
pub struct ClkTimeoutLimit(pub arbitrary_int::UInt<u32, 20>);
|
||||
|
||||
impl ClkTimeoutLimit {
|
||||
pub fn new(value: u20) -> Self {
|
||||
ClkTimeoutLimit(arbitrary_int::UInt::<u32, 20>::new(value.value()))
|
||||
}
|
||||
pub fn value(&self) -> u20 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub mod slave {
|
||||
use super::{Data, DataCount, FifoClear, RxFifoFullMode, TriggerLevel, TxFifoEmptyMode};
|
||||
use arbitrary_int::{u10, u11, u3, u4, u5};
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct Control {
|
||||
#[bit(0, r)]
|
||||
clk_enabled: bool,
|
||||
#[bit(1, r)]
|
||||
enabled: bool,
|
||||
#[bit(2, rw)]
|
||||
enable: bool,
|
||||
#[bit(3, rw)]
|
||||
tx_fifo_empty_mode: TxFifoEmptyMode,
|
||||
#[bit(4, rw)]
|
||||
rx_fifo_full_mode: RxFifoFullMode,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct Maxwords {
|
||||
#[bits(0..=10, rw)]
|
||||
maxwords: u11,
|
||||
#[bit(31, rw)]
|
||||
enable: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct Address {
|
||||
#[bit(0, rw)]
|
||||
rw: bool,
|
||||
#[bits(1..=10, rw)]
|
||||
address: u10,
|
||||
#[bit(15, rw)]
|
||||
a10_mode: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct AddressMask {
|
||||
/// Will normally be 0 to match both read and write addresses.
|
||||
#[bit(0, rw)]
|
||||
rw_mask: bool,
|
||||
/// Reset value 0x3FF.
|
||||
#[bits(1..=10, rw)]
|
||||
mask: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
pub enum Direction {
|
||||
#[default]
|
||||
MasterSend = 0,
|
||||
MasterReceive = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct LastAddress {
|
||||
#[bit(0, rw)]
|
||||
direction: Direction,
|
||||
#[bits(1..=10, rw)]
|
||||
address: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct Status {
|
||||
#[bit(0, r)]
|
||||
completed: bool,
|
||||
#[bit(1, r)]
|
||||
idle: bool,
|
||||
#[bit(2, r)]
|
||||
waiting: bool,
|
||||
#[bit(3, r)]
|
||||
tx_stalled: bool,
|
||||
#[bit(4, r)]
|
||||
rx_stalled: bool,
|
||||
#[bit(5, r)]
|
||||
address_match: bool,
|
||||
#[bit(6, r)]
|
||||
nack_data: bool,
|
||||
#[bit(7, r)]
|
||||
rx_data_first: bool,
|
||||
#[bit(8, r)]
|
||||
rx_not_empty: bool,
|
||||
#[bit(9, r)]
|
||||
rx_full: bool,
|
||||
#[bit(11, r)]
|
||||
rx_trigger: bool,
|
||||
#[bit(12, r)]
|
||||
tx_empty: bool,
|
||||
#[bit(13, r)]
|
||||
tx_not_full: bool,
|
||||
#[bit(15, r)]
|
||||
tx_trigger: bool,
|
||||
#[bit(28, r)]
|
||||
raw_busy: bool,
|
||||
#[bit(30, r)]
|
||||
raw_sda: bool,
|
||||
#[bit(31, r)]
|
||||
raw_scl: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct State {
|
||||
#[bits(0..=2, rw)]
|
||||
state: u3,
|
||||
#[bits(4..=7, rw)]
|
||||
step: u4,
|
||||
#[bits(8..=12, rw)]
|
||||
rx_fifo: u5,
|
||||
#[bits(14..=18, rw)]
|
||||
tx_fifo: u5,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct InterruptControl {
|
||||
#[bit(0, rw)]
|
||||
completed: bool,
|
||||
#[bit(1, rw)]
|
||||
idle: bool,
|
||||
#[bit(2, rw)]
|
||||
waiting: bool,
|
||||
#[bit(3, rw)]
|
||||
tx_stalled: bool,
|
||||
#[bit(4, rw)]
|
||||
rx_stalled: bool,
|
||||
#[bit(5, rw)]
|
||||
address_match: bool,
|
||||
#[bit(6, rw)]
|
||||
nack_data: bool,
|
||||
#[bit(7, rw)]
|
||||
rx_data_first: bool,
|
||||
|
||||
#[bit(8, rw)]
|
||||
i2c_start: bool,
|
||||
#[bit(9, rw)]
|
||||
i2c_stop: bool,
|
||||
#[bit(10, rw)]
|
||||
tx_underflow: bool,
|
||||
#[bit(11, rw)]
|
||||
rx_underflow: bool,
|
||||
#[bit(12, rw)]
|
||||
tx_ready: bool,
|
||||
#[bit(13, rw)]
|
||||
rx_ready: bool,
|
||||
#[bit(14, rw)]
|
||||
tx_empty: bool,
|
||||
#[bit(15, rw)]
|
||||
rx_full: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct InterruptStatus {
|
||||
#[bit(0, r)]
|
||||
completed: bool,
|
||||
#[bit(1, r)]
|
||||
idle: bool,
|
||||
#[bit(2, r)]
|
||||
waiting: bool,
|
||||
#[bit(3, r)]
|
||||
tx_stalled: bool,
|
||||
#[bit(4, r)]
|
||||
rx_stalled: bool,
|
||||
#[bit(5, r)]
|
||||
address_match: bool,
|
||||
#[bit(6, r)]
|
||||
nack_data: bool,
|
||||
#[bit(7, r)]
|
||||
rx_data_first: bool,
|
||||
|
||||
#[bit(8, r)]
|
||||
i2c_start: bool,
|
||||
#[bit(9, r)]
|
||||
i2c_stop: bool,
|
||||
#[bit(10, r)]
|
||||
tx_underflow: bool,
|
||||
#[bit(11, r)]
|
||||
rx_underflow: bool,
|
||||
#[bit(12, r)]
|
||||
tx_ready: bool,
|
||||
#[bit(13, r)]
|
||||
rx_ready: bool,
|
||||
#[bit(14, r)]
|
||||
tx_empty: bool,
|
||||
#[bit(15, r)]
|
||||
rx_full: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
#[derive(Debug)]
|
||||
pub struct InterruptClear {
|
||||
#[bit(0, w)]
|
||||
completed: bool,
|
||||
#[bit(1, w)]
|
||||
idle: bool,
|
||||
#[bit(2, w)]
|
||||
waiting: bool,
|
||||
#[bit(3, w)]
|
||||
tx_stalled: bool,
|
||||
#[bit(4, w)]
|
||||
rx_stalled: bool,
|
||||
#[bit(5, w)]
|
||||
address_match: bool,
|
||||
#[bit(6, w)]
|
||||
nack_data: bool,
|
||||
#[bit(7, w)]
|
||||
rx_data_first: bool,
|
||||
|
||||
#[bit(8, w)]
|
||||
i2c_start: bool,
|
||||
#[bit(9, w)]
|
||||
i2c_stop: bool,
|
||||
#[bit(10, w)]
|
||||
tx_underflow: bool,
|
||||
#[bit(11, w)]
|
||||
rx_underflow: bool,
|
||||
#[bit(12, w)]
|
||||
tx_ready: bool,
|
||||
#[bit(13, w)]
|
||||
rx_ready: bool,
|
||||
#[bit(14, w)]
|
||||
tx_empty: bool,
|
||||
#[bit(15, w)]
|
||||
rx_full: bool,
|
||||
}
|
||||
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C)]
|
||||
pub struct I2cSlave {
|
||||
s0_ctrl: Control,
|
||||
s0_maxwords: Maxwords,
|
||||
s0_address: Address,
|
||||
s0_addressmask: AddressMask,
|
||||
s0_data: Data,
|
||||
s0_lastaddress: LastAddress,
|
||||
#[mmio(PureRead)]
|
||||
s0_status: Status,
|
||||
#[mmio(PureRead)]
|
||||
s0_state: State,
|
||||
#[mmio(PureRead)]
|
||||
s0_tx_count: DataCount,
|
||||
#[mmio(PureRead)]
|
||||
s0_rx_count: DataCount,
|
||||
s0_irq_enb: InterruptControl,
|
||||
#[mmio(PureRead)]
|
||||
s0_irq_raw: InterruptStatus,
|
||||
#[mmio(PureRead)]
|
||||
s0_irq_status: InterruptStatus,
|
||||
#[mmio(Write)]
|
||||
s0_irq_clear: InterruptClear,
|
||||
s0_rx_fifo_trigger: TriggerLevel,
|
||||
s0_tx_fifo_trigger: TriggerLevel,
|
||||
#[mmio(Write)]
|
||||
s0_fifo_clear: FifoClear,
|
||||
s0_address_b: Address,
|
||||
s0_addressmask_b: AddressMask,
|
||||
}
|
||||
}
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[mmio(no_ctors)]
|
||||
#[repr(C)]
|
||||
pub struct I2c {
|
||||
control: Control,
|
||||
clkscale: ClkScale,
|
||||
words: Words,
|
||||
address: Address,
|
||||
data: Data,
|
||||
#[mmio(Write)]
|
||||
cmd: Command,
|
||||
#[mmio(PureRead)]
|
||||
status: Status,
|
||||
#[mmio(PureRead)]
|
||||
state: State,
|
||||
#[mmio(PureRead)]
|
||||
tx_count: DataCount,
|
||||
#[mmio(PureRead)]
|
||||
rx_count: DataCount,
|
||||
irq_enb: InterruptControl,
|
||||
#[mmio(PureRead)]
|
||||
irq_raw: InterruptStatus,
|
||||
#[mmio(PureRead)]
|
||||
irq_status: InterruptStatus,
|
||||
#[mmio(Write)]
|
||||
irq_clear: InterruptClear,
|
||||
rx_fifo_trigger: TriggerLevel,
|
||||
tx_fifo_trigger: TriggerLevel,
|
||||
#[mmio(Write)]
|
||||
fifo_clear: FifoClear,
|
||||
timing_config: TimingConfig,
|
||||
clk_timeout_limit: ClkTimeoutLimit,
|
||||
|
||||
_reserved_0: [u32; 0x2D],
|
||||
|
||||
#[mmio(inner)]
|
||||
slave: slave::I2cSlave,
|
||||
|
||||
#[cfg(feature = "vor1x")]
|
||||
_reserved_1: [u32; 0x3AC],
|
||||
#[cfg(feature = "vor4x")]
|
||||
_reserved_1: [u32; 0xAC],
|
||||
|
||||
/// Vorago 4x: 0x0214_07E9. Vorago 1x: 0x0014_07E1.
|
||||
#[mmio(PureRead)]
|
||||
perid: u32,
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "vor1x")] {
|
||||
static_assertions::const_assert_eq!(core::mem::size_of::<I2c>(), 0x1000);
|
||||
} else if #[cfg(feature = "vor4x")] {
|
||||
static_assertions::const_assert_eq!(core::mem::size_of::<I2c>(), 0x400);
|
||||
}
|
||||
}
|
||||
|
||||
impl I2c {
|
||||
fn new_mmio_at(base: usize) -> MmioI2c<'static> {
|
||||
MmioI2c {
|
||||
ptr: base as *mut _,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_mmio(bank: Bank) -> MmioI2c<'static> {
|
||||
match bank {
|
||||
Bank::I2c0 => Self::new_mmio_at(BASE_ADDR_0),
|
||||
Bank::I2c1 => Self::new_mmio_at(BASE_ADDR_1),
|
||||
#[cfg(feature = "vor4x")]
|
||||
Bank::Spi3 => Self::new_mmio_at(BASE_ADDR_2),
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,9 +1,13 @@
|
||||
#![no_std]
|
||||
pub mod gpio;
|
||||
pub mod i2c;
|
||||
pub mod ioconfig;
|
||||
pub mod pins;
|
||||
pub mod pwm;
|
||||
pub mod spi;
|
||||
pub mod sysconfig;
|
||||
pub mod time;
|
||||
pub mod timer;
|
||||
pub mod uart;
|
||||
|
||||
pub use sysconfig::{disable_peripheral_clock, enable_peripheral_clock};
|
||||
@@ -150,3 +154,29 @@ pub fn disable_nvic_interrupt(irq: va108xx::Interrupt) {
|
||||
pub(crate) mod sealed {
|
||||
pub trait Sealed {}
|
||||
}
|
||||
|
||||
pub(crate) mod shared {
|
||||
use arbitrary_int::u5;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TriggerLevel(arbitrary_int::UInt<u32, 5>);
|
||||
|
||||
impl TriggerLevel {
|
||||
pub const fn new(value: u5) -> Self {
|
||||
TriggerLevel(arbitrary_int::UInt::<u32, 5>::new(value.value() as u32))
|
||||
}
|
||||
|
||||
pub const fn value(&self) -> u5 {
|
||||
u5::new(self.0.value() as u8)
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
#[derive(Debug)]
|
||||
pub struct FifoClear {
|
||||
#[bit(1, w)]
|
||||
tx_fifo: bool,
|
||||
#[bit(0, w)]
|
||||
rx_fifo: bool,
|
||||
}
|
||||
}
|
||||
|
246
vorago-shared-periphs/src/pwm.rs
Normal file
246
vorago-shared-periphs/src/pwm.rs
Normal file
@@ -0,0 +1,246 @@
|
||||
use core::convert::Infallible;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::gpio::IoPeriphPin;
|
||||
use crate::timer::enable_tim_clk;
|
||||
use crate::timer::regs::{EnableControl, StatusSelect};
|
||||
use crate::{PeripheralSelect, enable_peripheral_clock};
|
||||
|
||||
use crate::time::Hertz;
|
||||
use crate::timer::{self, TimId, TimMarker, TimPin};
|
||||
|
||||
const DUTY_MAX: u16 = u16::MAX;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PwmA {}
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PwmB {}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[error("pin tim ID {pin_tim:?} and timer tim id {tim_id:?} do not match")]
|
||||
pub struct TimMissmatchError {
|
||||
pin_tim: TimId,
|
||||
tim_id: TimId,
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// PWM pin
|
||||
//==================================================================================================
|
||||
|
||||
/// Reduced version where type information is deleted
|
||||
pub struct PwmPin<Mode = PwmA> {
|
||||
tim_id: TimId,
|
||||
regs: timer::regs::MmioTimer<'static>,
|
||||
sys_clk: Hertz,
|
||||
/// For PWMB, this is the upper limit
|
||||
current_duty: u16,
|
||||
/// For PWMA, this value will not be used
|
||||
current_lower_limit: u16,
|
||||
current_period: Hertz,
|
||||
current_rst_val: u32,
|
||||
mode: PhantomData<Mode>,
|
||||
}
|
||||
|
||||
impl<Mode> PwmPin<Mode> {
|
||||
/// Create a new strongly typed PWM pin
|
||||
pub fn new<Pin: TimPin, Tim: TimMarker>(
|
||||
sys_clk: Hertz,
|
||||
_pin_and_tim: (Pin, Tim),
|
||||
initial_frequency: Hertz,
|
||||
) -> Result<Self, TimMissmatchError> {
|
||||
if Pin::TIM_ID != Tim::ID {
|
||||
return Err(TimMissmatchError {
|
||||
pin_tim: Pin::TIM_ID,
|
||||
tim_id: Tim::ID,
|
||||
});
|
||||
}
|
||||
IoPeriphPin::new(Pin::PIN_ID, Pin::FUN_SEL, None);
|
||||
let mut pin = PwmPin {
|
||||
tim_id: Tim::ID,
|
||||
regs: timer::regs::Timer::new_mmio(Tim::ID),
|
||||
current_duty: 0,
|
||||
current_lower_limit: 0,
|
||||
current_period: initial_frequency,
|
||||
current_rst_val: 0,
|
||||
sys_clk,
|
||||
mode: PhantomData,
|
||||
};
|
||||
enable_peripheral_clock(PeripheralSelect::Gpio);
|
||||
enable_peripheral_clock(PeripheralSelect::Ioconfig);
|
||||
enable_tim_clk(Tim::ID);
|
||||
pin.enable_pwm_a();
|
||||
pin.set_period(initial_frequency);
|
||||
Ok(pin)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn enable_pwm_a(&mut self) {
|
||||
self.regs.modify_control(|mut value| {
|
||||
value.set_status_sel(StatusSelect::PwmaOutput);
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn enable_pwm_b(&mut self) {
|
||||
self.regs.modify_control(|mut value| {
|
||||
value.set_status_sel(StatusSelect::PwmbOutput);
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_period(&self) -> Hertz {
|
||||
self.current_period
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_period(&mut self, period: impl Into<Hertz>) {
|
||||
self.current_period = period.into();
|
||||
// Avoid division by 0
|
||||
if self.current_period.raw() == 0 {
|
||||
return;
|
||||
}
|
||||
self.current_rst_val = self.sys_clk.raw() / self.current_period.raw();
|
||||
self.regs.write_reset_value(self.current_rst_val);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn disable(&mut self) {
|
||||
self.regs.write_enable_control(EnableControl::new_disable());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn enable(&mut self) {
|
||||
self.regs.write_enable_control(EnableControl::new_enable());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn period(&self) -> Hertz {
|
||||
self.current_period
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn duty(&self) -> u16 {
|
||||
self.current_duty
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PwmPin<PwmA>> for PwmPin<PwmB> {
|
||||
fn from(other: PwmPin<PwmA>) -> Self {
|
||||
let mut pwmb = Self {
|
||||
mode: PhantomData,
|
||||
regs: other.regs,
|
||||
tim_id: other.tim_id,
|
||||
sys_clk: other.sys_clk,
|
||||
current_duty: other.current_duty,
|
||||
current_lower_limit: other.current_lower_limit,
|
||||
current_period: other.current_period,
|
||||
current_rst_val: other.current_rst_val,
|
||||
};
|
||||
pwmb.enable_pwm_b();
|
||||
pwmb
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PwmPin<PwmB>> for PwmPin<PwmA> {
|
||||
fn from(other: PwmPin<PwmB>) -> Self {
|
||||
let mut pwmb = Self {
|
||||
mode: PhantomData,
|
||||
tim_id: other.tim_id,
|
||||
regs: other.regs,
|
||||
sys_clk: other.sys_clk,
|
||||
current_duty: other.current_duty,
|
||||
current_lower_limit: other.current_lower_limit,
|
||||
current_period: other.current_period,
|
||||
current_rst_val: other.current_rst_val,
|
||||
};
|
||||
pwmb.enable_pwm_a();
|
||||
pwmb
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// PWMB implementations
|
||||
//==================================================================================================
|
||||
|
||||
impl PwmPin<PwmB> {
|
||||
#[inline(always)]
|
||||
pub fn pwmb_lower_limit(&self) -> u16 {
|
||||
self.current_lower_limit
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn pwmb_upper_limit(&self) -> u16 {
|
||||
self.current_duty
|
||||
}
|
||||
|
||||
/// Set the lower limit for PWMB
|
||||
///
|
||||
/// The PWM signal will be 1 as long as the current RST counter is larger than
|
||||
/// the lower limit. For example, with a lower limit of 0.5 and and an upper limit
|
||||
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
|
||||
/// state
|
||||
#[inline(always)]
|
||||
pub fn set_pwmb_lower_limit(&mut self, duty: u16) {
|
||||
self.current_lower_limit = duty;
|
||||
let pwmb_val: u64 =
|
||||
(self.current_rst_val as u64 * self.current_lower_limit as u64) / DUTY_MAX as u64;
|
||||
self.regs.write_pwmb_value(pwmb_val as u32);
|
||||
}
|
||||
|
||||
/// Set the higher limit for PWMB
|
||||
///
|
||||
/// The PWM signal will be 1 as long as the current RST counter is smaller than
|
||||
/// the higher limit. For example, with a lower limit of 0.5 and and an upper limit
|
||||
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
|
||||
/// state
|
||||
pub fn set_pwmb_upper_limit(&mut self, duty: u16) {
|
||||
self.current_duty = duty;
|
||||
let pwma_val: u64 =
|
||||
(self.current_rst_val as u64 * self.current_duty as u64) / DUTY_MAX as u64;
|
||||
self.regs.write_pwma_value(pwma_val as u32);
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Embedded HAL implementation: PWMA only
|
||||
//==================================================================================================
|
||||
|
||||
impl embedded_hal::pwm::ErrorType for PwmPin {
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
impl embedded_hal::pwm::SetDutyCycle for PwmPin {
|
||||
#[inline]
|
||||
fn max_duty_cycle(&self) -> u16 {
|
||||
DUTY_MAX
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
|
||||
self.current_duty = duty;
|
||||
let pwma_val: u64 = (self.current_rst_val as u64
|
||||
* (DUTY_MAX as u64 - self.current_duty as u64))
|
||||
/ DUTY_MAX as u64;
|
||||
self.regs.write_pwma_value(pwma_val as u32);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the corresponding u16 duty cycle from a percent value ranging between 0.0 and 1.0.
|
||||
///
|
||||
/// Please note that this might load a lot of floating point code because this processor does not
|
||||
/// have a FPU
|
||||
pub fn get_duty_from_percent(percent: f32) -> u16 {
|
||||
if percent > 1.0 {
|
||||
DUTY_MAX
|
||||
} else if percent <= 0.0 {
|
||||
0
|
||||
} else {
|
||||
(percent * DUTY_MAX as f32) as u16
|
||||
}
|
||||
}
|
868
vorago-shared-periphs/src/spi/mod.rs
Normal file
868
vorago-shared-periphs/src/spi/mod.rs
Normal file
@@ -0,0 +1,868 @@
|
||||
use crate::gpio::{IoPeriphPin, PinId};
|
||||
use crate::FunSel;
|
||||
use crate::{
|
||||
enable_peripheral_clock, pins::PinMarker, sealed::Sealed, time::Hertz, PeripheralSelect,
|
||||
};
|
||||
use core::{convert::Infallible, fmt::Debug, marker::PhantomData};
|
||||
use embedded_hal::spi::{Mode, MODE_0};
|
||||
|
||||
use regs::{ClkPrescaler, Data, FifoClear, WordSize};
|
||||
#[cfg(feature = "vor1x")]
|
||||
use va108xx as pac;
|
||||
#[cfg(feature = "vor1x")]
|
||||
pub mod pins_vor1x;
|
||||
|
||||
pub use regs::{Bank, HwChipSelectId};
|
||||
|
||||
pub mod regs;
|
||||
|
||||
pub fn configure_pin_as_hw_cs_pin<P: PinMarker + HwCsProvider>(_pin: P) -> HwChipSelectId {
|
||||
IoPeriphPin::new(P::ID, P::FUN_SEL, None);
|
||||
P::CS_ID
|
||||
}
|
||||
|
||||
pub trait PinSck: PinMarker {
|
||||
const SPI_ID: Bank;
|
||||
const FUN_SEL: FunSel;
|
||||
}
|
||||
|
||||
pub trait PinMosi: PinMarker {
|
||||
const SPI_ID: Bank;
|
||||
const FUN_SEL: FunSel;
|
||||
}
|
||||
|
||||
pub trait PinMiso: PinMarker {
|
||||
const SPI_ID: Bank;
|
||||
const FUN_SEL: FunSel;
|
||||
}
|
||||
|
||||
pub trait HwCsProvider {
|
||||
const PIN_ID: PinId;
|
||||
const SPI_ID: Bank;
|
||||
const FUN_SEL: FunSel;
|
||||
const CS_ID: HwChipSelectId;
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Defintions
|
||||
//==================================================================================================
|
||||
|
||||
// FIFO has a depth of 16.
|
||||
const FILL_DEPTH: usize = 12;
|
||||
|
||||
pub const BMSTART_BMSTOP_MASK: u32 = 1 << 31;
|
||||
pub const BMSKIPDATA_MASK: u32 = 1 << 30;
|
||||
|
||||
pub const DEFAULT_CLK_DIV: u16 = 2;
|
||||
|
||||
/// Common trait implemented by all PAC peripheral access structures. The register block
|
||||
/// format is the same for all SPI blocks.
|
||||
pub trait SpiMarker: Sealed {
|
||||
const ID: Bank;
|
||||
const PERIPH_SEL: PeripheralSelect;
|
||||
}
|
||||
|
||||
impl SpiMarker for pac::Spia {
|
||||
const ID: Bank = Bank::Spi0;
|
||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi0;
|
||||
}
|
||||
impl Sealed for pac::Spia {}
|
||||
|
||||
impl SpiMarker for pac::Spib {
|
||||
const ID: Bank = Bank::Spi1;
|
||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi1;
|
||||
}
|
||||
impl Sealed for pac::Spib {}
|
||||
|
||||
impl SpiMarker for pac::Spic {
|
||||
const ID: Bank = Bank::Spi2;
|
||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi2;
|
||||
}
|
||||
impl Sealed for pac::Spic {}
|
||||
|
||||
//==================================================================================================
|
||||
// Config
|
||||
//==================================================================================================
|
||||
|
||||
pub trait TransferConfigProvider {
|
||||
fn sod(&mut self, sod: bool);
|
||||
fn blockmode(&mut self, blockmode: bool);
|
||||
fn mode(&mut self, mode: Mode);
|
||||
fn clk_cfg(&mut self, clk_cfg: SpiClkConfig);
|
||||
fn hw_cs_id(&self) -> u8;
|
||||
}
|
||||
|
||||
/// Type erased variant of the transfer configuration. This is required to avoid generics in
|
||||
/// the SPI constructor.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct TransferConfig {
|
||||
pub clk_cfg: Option<SpiClkConfig>,
|
||||
pub mode: Option<Mode>,
|
||||
pub sod: bool,
|
||||
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
|
||||
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
|
||||
/// duration of multiple data words
|
||||
pub blockmode: bool,
|
||||
/// Only used when blockmode is used. The SCK will be stalled until an explicit stop bit
|
||||
/// is set on a written word.
|
||||
pub bmstall: bool,
|
||||
pub hw_cs: Option<HwChipSelectId>,
|
||||
}
|
||||
|
||||
impl TransferConfig {
|
||||
pub fn new_with_hw_cs(
|
||||
clk_cfg: Option<SpiClkConfig>,
|
||||
mode: Option<Mode>,
|
||||
blockmode: bool,
|
||||
bmstall: bool,
|
||||
sod: bool,
|
||||
hw_cs_id: HwChipSelectId,
|
||||
) -> Self {
|
||||
TransferConfig {
|
||||
clk_cfg,
|
||||
mode,
|
||||
sod,
|
||||
blockmode,
|
||||
bmstall,
|
||||
hw_cs: Some(hw_cs_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct SpiConfig {
|
||||
clk: SpiClkConfig,
|
||||
// SPI mode configuration
|
||||
pub init_mode: Mode,
|
||||
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
|
||||
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
|
||||
/// duration of multiple data words. Defaults to true.
|
||||
pub blockmode: bool,
|
||||
/// This enables the stalling of the SPI SCK if in blockmode and the FIFO is empty.
|
||||
/// Currently enabled by default.
|
||||
pub bmstall: bool,
|
||||
/// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control
|
||||
pub slave_output_disable: bool,
|
||||
/// Loopback mode. If you use this, don't connect MISO to MOSI, they will be tied internally
|
||||
pub loopback_mode: bool,
|
||||
/// Enable Master Delayer Capture Mode. See Programmers Guide p.92 for more details
|
||||
pub master_delayer_capture: bool,
|
||||
}
|
||||
|
||||
impl Default for SpiConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
init_mode: MODE_0,
|
||||
blockmode: true,
|
||||
bmstall: true,
|
||||
// Default value is definitely valid.
|
||||
clk: SpiClkConfig::from_div(DEFAULT_CLK_DIV).unwrap(),
|
||||
slave_output_disable: Default::default(),
|
||||
loopback_mode: Default::default(),
|
||||
master_delayer_capture: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpiConfig {
|
||||
pub fn loopback(mut self, enable: bool) -> Self {
|
||||
self.loopback_mode = enable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn blockmode(mut self, enable: bool) -> Self {
|
||||
self.blockmode = enable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bmstall(mut self, enable: bool) -> Self {
|
||||
self.bmstall = enable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mode(mut self, mode: Mode) -> Self {
|
||||
self.init_mode = mode;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn clk_cfg(mut self, clk_cfg: SpiClkConfig) -> Self {
|
||||
self.clk = clk_cfg;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn slave_output_disable(mut self, sod: bool) -> Self {
|
||||
self.slave_output_disable = sod;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Word Size
|
||||
//==================================================================================================
|
||||
|
||||
/// Configuration trait for the Word Size
|
||||
/// used by the SPI peripheral
|
||||
pub trait WordProvider: Copy + Default + Into<u32> + TryFrom<u32> + 'static {
|
||||
const MASK: u32;
|
||||
const WORD_SIZE: regs::WordSize;
|
||||
fn word_reg() -> u8;
|
||||
}
|
||||
|
||||
impl WordProvider for u8 {
|
||||
const MASK: u32 = 0xff;
|
||||
const WORD_SIZE: regs::WordSize = regs::WordSize::EightBits;
|
||||
fn word_reg() -> u8 {
|
||||
0x07
|
||||
}
|
||||
}
|
||||
|
||||
impl WordProvider for u16 {
|
||||
const MASK: u32 = 0xffff;
|
||||
const WORD_SIZE: regs::WordSize = regs::WordSize::SixteenBits;
|
||||
fn word_reg() -> u8 {
|
||||
0x0f
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Spi
|
||||
//==================================================================================================
|
||||
|
||||
/// Low level access trait for the SPI peripheral.
|
||||
pub trait SpiLowLevel {
|
||||
/// Low level function to write a word to the SPI FIFO but also checks whether
|
||||
/// there is actually data in the FIFO.
|
||||
///
|
||||
/// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
|
||||
fn write_fifo(&mut self, data: u32) -> nb::Result<(), Infallible>;
|
||||
|
||||
/// Low level function to write a word to the SPI FIFO without checking whether
|
||||
/// there FIFO is full.
|
||||
///
|
||||
/// This does not necesarily mean there is a space in the FIFO available.
|
||||
/// Use [Self::write_fifo] function to write a word into the FIFO reliably.
|
||||
fn write_fifo_unchecked(&mut self, data: u32);
|
||||
|
||||
/// Low level function to read a word from the SPI FIFO. Must be preceeded by a
|
||||
/// [Self::write_fifo] call.
|
||||
///
|
||||
/// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
|
||||
fn read_fifo(&mut self) -> nb::Result<u32, Infallible>;
|
||||
|
||||
/// Low level function to read a word from from the SPI FIFO.
|
||||
///
|
||||
/// This does not necesarily mean there is a word in the FIFO available.
|
||||
/// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb]
|
||||
/// API.
|
||||
/// You might also need to mask the value to ignore the BMSTART/BMSTOP bit.
|
||||
fn read_fifo_unchecked(&mut self) -> u32;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) {
|
||||
match mode {
|
||||
embedded_hal::spi::MODE_0 => (false, false),
|
||||
embedded_hal::spi::MODE_1 => (false, true),
|
||||
embedded_hal::spi::MODE_2 => (true, false),
|
||||
embedded_hal::spi::MODE_3 => (true, true),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct SpiClkConfig {
|
||||
prescale_val: u8,
|
||||
scrdv: u8,
|
||||
}
|
||||
|
||||
impl SpiClkConfig {
|
||||
pub fn prescale_val(&self) -> u8 {
|
||||
self.prescale_val
|
||||
}
|
||||
pub fn scrdv(&self) -> u8 {
|
||||
self.scrdv
|
||||
}
|
||||
}
|
||||
|
||||
impl SpiClkConfig {
|
||||
pub fn new(prescale_val: u8, scrdv: u8) -> Self {
|
||||
Self {
|
||||
prescale_val,
|
||||
scrdv,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_div(div: u16) -> Result<Self, SpiClkConfigError> {
|
||||
spi_clk_config_from_div(div)
|
||||
}
|
||||
|
||||
pub fn from_clk(sys_clk: impl Into<Hertz>, spi_clk: impl Into<Hertz>) -> Option<Self> {
|
||||
clk_div_for_target_clock(sys_clk, spi_clk).map(|div| spi_clk_config_from_div(div).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum SpiClkConfigError {
|
||||
#[error("division by zero")]
|
||||
DivIsZero,
|
||||
#[error("divide value is not even")]
|
||||
DivideValueNotEven,
|
||||
#[error("scrdv value is too large")]
|
||||
ScrdvValueTooLarge,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn spi_clk_config_from_div(mut div: u16) -> Result<SpiClkConfig, SpiClkConfigError> {
|
||||
if div == 0 {
|
||||
return Err(SpiClkConfigError::DivIsZero);
|
||||
}
|
||||
if div % 2 != 0 {
|
||||
return Err(SpiClkConfigError::DivideValueNotEven);
|
||||
}
|
||||
let mut prescale_val = 0;
|
||||
|
||||
// find largest (even) prescale value that divides into div
|
||||
for i in (2..=0xfe).rev().step_by(2) {
|
||||
if div % i == 0 {
|
||||
prescale_val = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if prescale_val == 0 {
|
||||
return Err(SpiClkConfigError::DivideValueNotEven);
|
||||
}
|
||||
|
||||
div /= prescale_val;
|
||||
if div > u8::MAX as u16 + 1 {
|
||||
return Err(SpiClkConfigError::ScrdvValueTooLarge);
|
||||
}
|
||||
Ok(SpiClkConfig {
|
||||
prescale_val: prescale_val as u8,
|
||||
scrdv: (div - 1) as u8,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clk_div_for_target_clock(
|
||||
sys_clk: impl Into<Hertz>,
|
||||
spi_clk: impl Into<Hertz>,
|
||||
) -> Option<u16> {
|
||||
let spi_clk = spi_clk.into();
|
||||
let sys_clk = sys_clk.into();
|
||||
if spi_clk > sys_clk {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Step 1: Calculate raw divider.
|
||||
let raw_div = sys_clk.raw() / spi_clk.raw();
|
||||
let remainder = sys_clk.raw() % spi_clk.raw();
|
||||
|
||||
// Step 2: Round up if necessary.
|
||||
let mut rounded_div = if remainder * 2 >= spi_clk.raw() {
|
||||
raw_div + 1
|
||||
} else {
|
||||
raw_div
|
||||
};
|
||||
|
||||
if rounded_div % 2 != 0 {
|
||||
// Take slower clock conservatively.
|
||||
rounded_div += 1;
|
||||
}
|
||||
if rounded_div > u16::MAX as u32 {
|
||||
return None;
|
||||
}
|
||||
Some(rounded_div as u16)
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("peripheral or peripheral pin ID is not consistent")]
|
||||
pub struct SpiIdMissmatchError;
|
||||
|
||||
/// SPI peripheral driver structure.
|
||||
pub struct Spi<Word = u8> {
|
||||
id: Bank,
|
||||
regs: regs::MmioSpi<'static>,
|
||||
cfg: SpiConfig,
|
||||
sys_clk: Hertz,
|
||||
/// Fill word for read-only SPI transactions.
|
||||
fill_word: Word,
|
||||
blockmode: bool,
|
||||
bmstall: bool,
|
||||
word: PhantomData<Word>,
|
||||
}
|
||||
|
||||
impl<Word: WordProvider> Spi<Word>
|
||||
where
|
||||
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
||||
{
|
||||
/// Create a new SPI struct.
|
||||
///
|
||||
/// ## Arguments
|
||||
/// * `sys_clk` - System clock
|
||||
/// * `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
|
||||
pub fn new_for_rom<SpiI: SpiMarker>(
|
||||
sys_clk: Hertz,
|
||||
spi: SpiI,
|
||||
spi_cfg: SpiConfig,
|
||||
) -> Result<Self, SpiIdMissmatchError> {
|
||||
#[cfg(feature = "vor1x")]
|
||||
if SpiI::ID != Bank::Spi2 {
|
||||
return Err(SpiIdMissmatchError);
|
||||
}
|
||||
#[cfg(feature = "vor4x")]
|
||||
if SpiI::ID != Bank::Spi3 {
|
||||
return Err(SpiIdMissmatchError);
|
||||
}
|
||||
Ok(Self::new_generic(sys_clk, spi, spi_cfg))
|
||||
}
|
||||
/// Create a new SPI struct.
|
||||
///
|
||||
/// ## Arguments
|
||||
/// * `sys_clk` - System clock
|
||||
/// * `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
|
||||
pub fn new<SpiI: SpiMarker, Sck: PinSck, Miso: PinMiso, Mosi: PinMosi>(
|
||||
sys_clk: Hertz,
|
||||
spi: SpiI,
|
||||
_pins: (Sck, Miso, Mosi),
|
||||
spi_cfg: SpiConfig,
|
||||
) -> Result<Self, SpiIdMissmatchError> {
|
||||
if SpiI::ID != Sck::SPI_ID || SpiI::ID != Miso::SPI_ID || SpiI::ID != Mosi::SPI_ID {
|
||||
return Err(SpiIdMissmatchError);
|
||||
}
|
||||
IoPeriphPin::new(Sck::ID, Sck::FUN_SEL, None);
|
||||
IoPeriphPin::new(Miso::ID, Miso::FUN_SEL, None);
|
||||
IoPeriphPin::new(Mosi::ID, Mosi::FUN_SEL, None);
|
||||
Ok(Self::new_generic(sys_clk, spi, spi_cfg))
|
||||
}
|
||||
|
||||
pub fn new_generic<SpiI: SpiMarker>(sys_clk: Hertz, _spi: SpiI, spi_cfg: SpiConfig) -> Self {
|
||||
enable_peripheral_clock(SpiI::PERIPH_SEL);
|
||||
let mut regs = regs::Spi::new_mmio(SpiI::ID);
|
||||
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(spi_cfg.init_mode);
|
||||
regs.write_ctrl0(
|
||||
regs::Control0::builder()
|
||||
.with_scrdv(spi_cfg.clk.scrdv)
|
||||
.with_sph(cph_bit)
|
||||
.with_spo(cpo_bit)
|
||||
.with_word_size(Word::WORD_SIZE)
|
||||
.build(),
|
||||
);
|
||||
regs.write_ctrl1(
|
||||
regs::Control1::builder()
|
||||
.with_mtxpause(false)
|
||||
.with_mdlycap(spi_cfg.master_delayer_capture)
|
||||
.with_bm_stall(spi_cfg.bmstall)
|
||||
.with_bm_start(false)
|
||||
.with_blockmode(spi_cfg.blockmode)
|
||||
.with_ss(HwChipSelectId::Id0)
|
||||
.with_sod(spi_cfg.slave_output_disable)
|
||||
.with_slave_mode(false)
|
||||
.with_enable(false)
|
||||
.with_lbm(spi_cfg.loopback_mode)
|
||||
.build(),
|
||||
);
|
||||
regs.write_clkprescale(ClkPrescaler::new(spi_cfg.clk.prescale_val));
|
||||
regs.write_fifo_clear(
|
||||
FifoClear::builder()
|
||||
.with_tx_fifo(true)
|
||||
.with_rx_fifo(true)
|
||||
.build(),
|
||||
);
|
||||
// Enable the peripheral as the last step as recommended in the
|
||||
// programmers guide
|
||||
regs.modify_ctrl1(|mut value| {
|
||||
value.set_enable(true);
|
||||
value
|
||||
});
|
||||
Spi {
|
||||
id: SpiI::ID,
|
||||
regs: regs::Spi::new_mmio(SpiI::ID),
|
||||
cfg: spi_cfg,
|
||||
sys_clk,
|
||||
fill_word: Default::default(),
|
||||
bmstall: spi_cfg.bmstall,
|
||||
blockmode: spi_cfg.blockmode,
|
||||
word: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cfg_clock(&mut self, cfg: SpiClkConfig) {
|
||||
self.regs.modify_ctrl0(|mut value| {
|
||||
value.set_scrdv(cfg.scrdv);
|
||||
value
|
||||
});
|
||||
self.regs
|
||||
.write_clkprescale(regs::ClkPrescaler::new(cfg.prescale_val));
|
||||
}
|
||||
|
||||
pub fn set_fill_word(&mut self, fill_word: Word) {
|
||||
self.fill_word = fill_word;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> {
|
||||
let val = spi_clk_config_from_div(div)?;
|
||||
self.cfg_clock(val);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cfg_mode(&mut self, mode: Mode) {
|
||||
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode);
|
||||
self.regs.modify_ctrl0(|mut value| {
|
||||
value.set_spo(cpo_bit);
|
||||
value.set_sph(cph_bit);
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fill_word(&self) -> Word {
|
||||
self.fill_word
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clear_tx_fifo(&mut self) {
|
||||
self.regs.write_fifo_clear(
|
||||
regs::FifoClear::builder()
|
||||
.with_tx_fifo(true)
|
||||
.with_rx_fifo(false)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clear_rx_fifo(&mut self) {
|
||||
self.regs.write_fifo_clear(
|
||||
regs::FifoClear::builder()
|
||||
.with_tx_fifo(false)
|
||||
.with_rx_fifo(true)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn perid(&self) -> u32 {
|
||||
self.regs.read_perid()
|
||||
}
|
||||
|
||||
/// Configure the hardware chip select given a hardware chip select ID.
|
||||
///
|
||||
/// The pin also needs to be configured to be used as a HW CS pin. This can be done
|
||||
/// by using the [configure_pin_as_hw_cs_pin] function which also returns the
|
||||
/// corresponding [HwChipSelectId].
|
||||
#[inline]
|
||||
pub fn cfg_hw_cs(&mut self, hw_cs: HwChipSelectId) {
|
||||
self.regs.modify_ctrl1(|mut value| {
|
||||
value.set_sod(false);
|
||||
value.set_ss(hw_cs);
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
/// Disables the hardware chip select functionality. This can be used when performing
|
||||
/// external chip select handling, for example with GPIO pins.
|
||||
#[inline]
|
||||
pub fn cfg_hw_cs_disable(&mut self) {
|
||||
self.regs.modify_ctrl1(|mut value| {
|
||||
value.set_sod(true);
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
/// Utility function to configure all relevant transfer parameters in one go.
|
||||
/// This is useful if multiple devices with different clock and mode configurations
|
||||
/// are connected to one bus.
|
||||
pub fn cfg_transfer(&mut self, transfer_cfg: &TransferConfig) {
|
||||
if let Some(trans_clk_div) = transfer_cfg.clk_cfg {
|
||||
self.cfg_clock(trans_clk_div);
|
||||
}
|
||||
if let Some(mode) = transfer_cfg.mode {
|
||||
self.cfg_mode(mode);
|
||||
}
|
||||
self.blockmode = transfer_cfg.blockmode;
|
||||
self.regs.modify_ctrl1(|mut value| {
|
||||
if transfer_cfg.sod {
|
||||
value.set_sod(transfer_cfg.sod);
|
||||
} else {
|
||||
value.set_sod(false);
|
||||
if let Some(hw_cs) = transfer_cfg.hw_cs {
|
||||
value.set_ss(hw_cs);
|
||||
}
|
||||
}
|
||||
value.set_blockmode(transfer_cfg.blockmode);
|
||||
value.set_bm_stall(transfer_cfg.bmstall);
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
fn flush_internal(&mut self) {
|
||||
let mut status_reg = self.regs.read_status();
|
||||
while !status_reg.tx_empty() || status_reg.rx_not_empty() || status_reg.busy() {
|
||||
if status_reg.rx_not_empty() {
|
||||
self.read_fifo_unchecked();
|
||||
}
|
||||
status_reg = self.regs.read_status();
|
||||
}
|
||||
}
|
||||
|
||||
fn transfer_preparation(&mut self, words: &[Word]) -> Result<(), Infallible> {
|
||||
if words.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
self.flush_internal();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// The FIFO can hold a guaranteed amount of data, so we can pump it on transfer
|
||||
// initialization. Returns the amount of written bytes.
|
||||
fn initial_send_fifo_pumping_with_words(&mut self, words: &[Word]) -> usize {
|
||||
//let reg_block = self.reg_block();
|
||||
if self.blockmode {
|
||||
self.regs.modify_ctrl1(|mut value| {
|
||||
value.set_mtxpause(true);
|
||||
value
|
||||
});
|
||||
}
|
||||
// Fill the first half of the write FIFO
|
||||
let mut current_write_idx = 0;
|
||||
let smaller_idx = core::cmp::min(FILL_DEPTH, words.len());
|
||||
for _ in 0..smaller_idx {
|
||||
if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall {
|
||||
self.write_fifo_unchecked(words[current_write_idx].into() | BMSTART_BMSTOP_MASK);
|
||||
} else {
|
||||
self.write_fifo_unchecked(words[current_write_idx].into());
|
||||
}
|
||||
current_write_idx += 1;
|
||||
}
|
||||
if self.blockmode {
|
||||
self.regs.modify_ctrl1(|mut value| {
|
||||
value.set_mtxpause(false);
|
||||
value
|
||||
});
|
||||
}
|
||||
current_write_idx
|
||||
}
|
||||
|
||||
// The FIFO can hold a guaranteed amount of data, so we can pump it on transfer
|
||||
// initialization.
|
||||
fn initial_send_fifo_pumping_with_fill_words(&mut self, send_len: usize) -> usize {
|
||||
if self.blockmode {
|
||||
self.regs.modify_ctrl1(|mut value| {
|
||||
value.set_mtxpause(true);
|
||||
value
|
||||
});
|
||||
}
|
||||
// Fill the first half of the write FIFO
|
||||
let mut current_write_idx = 0;
|
||||
let smaller_idx = core::cmp::min(FILL_DEPTH, send_len);
|
||||
for _ in 0..smaller_idx {
|
||||
if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall {
|
||||
self.write_fifo_unchecked(self.fill_word.into() | BMSTART_BMSTOP_MASK);
|
||||
} else {
|
||||
self.write_fifo_unchecked(self.fill_word.into());
|
||||
}
|
||||
current_write_idx += 1;
|
||||
}
|
||||
if self.blockmode {
|
||||
self.regs.modify_ctrl1(|mut value| {
|
||||
value.set_mtxpause(false);
|
||||
value
|
||||
});
|
||||
}
|
||||
current_write_idx
|
||||
}
|
||||
}
|
||||
|
||||
impl<Word: WordProvider> SpiLowLevel for Spi<Word>
|
||||
where
|
||||
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn write_fifo(&mut self, data: u32) -> nb::Result<(), Infallible> {
|
||||
if !self.regs.read_status().tx_not_full() {
|
||||
return Err(nb::Error::WouldBlock);
|
||||
}
|
||||
self.write_fifo_unchecked(data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn write_fifo_unchecked(&mut self, data: u32) {
|
||||
self.regs.write_data(Data::new_with_raw_value(data));
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn read_fifo(&mut self) -> nb::Result<u32, Infallible> {
|
||||
if !self.regs.read_status().rx_not_empty() {
|
||||
return Err(nb::Error::WouldBlock);
|
||||
}
|
||||
Ok(self.read_fifo_unchecked())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn read_fifo_unchecked(&mut self) -> u32 {
|
||||
self.regs.read_data().raw_value()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Word: WordProvider> embedded_hal::spi::ErrorType for Spi<Word> {
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
impl<Word: WordProvider> embedded_hal::spi::SpiBus<Word> for Spi<Word>
|
||||
where
|
||||
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
||||
{
|
||||
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
||||
self.transfer_preparation(words)?;
|
||||
let mut current_read_idx = 0;
|
||||
let mut current_write_idx = self.initial_send_fifo_pumping_with_fill_words(words.len());
|
||||
loop {
|
||||
if current_read_idx < words.len() {
|
||||
words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
current_read_idx += 1;
|
||||
}
|
||||
if current_write_idx < words.len() {
|
||||
if current_write_idx == words.len() - 1 && self.bmstall {
|
||||
nb::block!(self.write_fifo(self.fill_word.into() | BMSTART_BMSTOP_MASK))?;
|
||||
} else {
|
||||
nb::block!(self.write_fifo(self.fill_word.into()))?;
|
||||
}
|
||||
current_write_idx += 1;
|
||||
}
|
||||
if current_read_idx >= words.len() && current_write_idx >= words.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
|
||||
self.transfer_preparation(words)?;
|
||||
let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words);
|
||||
while current_write_idx < words.len() {
|
||||
if current_write_idx == words.len() - 1 && self.bmstall {
|
||||
nb::block!(self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK))?;
|
||||
} else {
|
||||
nb::block!(self.write_fifo(words[current_write_idx].into()))?;
|
||||
}
|
||||
current_write_idx += 1;
|
||||
// Ignore received words.
|
||||
if self.regs.read_status().rx_not_empty() {
|
||||
self.clear_rx_fifo();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
|
||||
self.transfer_preparation(write)?;
|
||||
let mut current_read_idx = 0;
|
||||
let mut current_write_idx = self.initial_send_fifo_pumping_with_words(write);
|
||||
while current_read_idx < read.len() || current_write_idx < write.len() {
|
||||
if current_write_idx < write.len() {
|
||||
if current_write_idx == write.len() - 1 && self.bmstall {
|
||||
nb::block!(
|
||||
self.write_fifo(write[current_write_idx].into() | BMSTART_BMSTOP_MASK)
|
||||
)?;
|
||||
} else {
|
||||
nb::block!(self.write_fifo(write[current_write_idx].into()))?;
|
||||
}
|
||||
current_write_idx += 1;
|
||||
}
|
||||
if current_read_idx < read.len() {
|
||||
read[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
current_read_idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
||||
self.transfer_preparation(words)?;
|
||||
let mut current_read_idx = 0;
|
||||
let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words);
|
||||
|
||||
while current_read_idx < words.len() || current_write_idx < words.len() {
|
||||
if current_write_idx < words.len() {
|
||||
if current_write_idx == words.len() - 1 && self.bmstall {
|
||||
nb::block!(
|
||||
self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK)
|
||||
)?;
|
||||
} else {
|
||||
nb::block!(self.write_fifo(words[current_write_idx].into()))?;
|
||||
}
|
||||
current_write_idx += 1;
|
||||
}
|
||||
if current_read_idx < words.len() && current_read_idx < current_write_idx {
|
||||
words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
current_read_idx += 1;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
self.flush_internal();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Changing the word size also requires a type conversion
|
||||
impl From<Spi<u8>> for Spi<u16> {
|
||||
fn from(mut old_spi: Spi<u8>) -> Self {
|
||||
old_spi.regs.modify_ctrl0(|mut value| {
|
||||
value.set_word_size(WordSize::SixteenBits);
|
||||
value
|
||||
});
|
||||
Spi {
|
||||
id: old_spi.id,
|
||||
regs: old_spi.regs,
|
||||
cfg: old_spi.cfg,
|
||||
blockmode: old_spi.blockmode,
|
||||
fill_word: Default::default(),
|
||||
bmstall: old_spi.bmstall,
|
||||
sys_clk: old_spi.sys_clk,
|
||||
word: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Spi<u16>> for Spi<u8> {
|
||||
fn from(mut old_spi: Spi<u16>) -> Self {
|
||||
old_spi.regs.modify_ctrl0(|mut value| {
|
||||
value.set_word_size(WordSize::EightBits);
|
||||
value
|
||||
});
|
||||
Spi {
|
||||
id: old_spi.id,
|
||||
regs: old_spi.regs,
|
||||
cfg: old_spi.cfg,
|
||||
blockmode: old_spi.blockmode,
|
||||
fill_word: Default::default(),
|
||||
bmstall: old_spi.bmstall,
|
||||
sys_clk: old_spi.sys_clk,
|
||||
word: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,45 +1,24 @@
|
||||
use vorago_shared_periphs::gpio::{PinId, PinIdProvider};
|
||||
use vorago_shared_periphs::FunSel;
|
||||
use super::{HwCsProvider, PinMiso, PinMosi, PinSck};
|
||||
use crate::FunSel;
|
||||
use crate::gpio::{PinId, PinIdProvider};
|
||||
|
||||
use crate::{
|
||||
pins::{
|
||||
Pa10, Pa11, Pa12, Pa13, Pa14, Pa15, Pa16, Pa17, Pa18, Pa19, Pa20, Pa21, Pa22, Pa23, Pa24,
|
||||
Pa25, Pa26, Pa27, Pa28, Pa29, Pa30, Pa31, Pb0, Pb1, Pb10, Pb11, Pb12, Pb13, Pb14, Pb15,
|
||||
Pb16, Pb17, Pb18, Pb19, Pb2, Pb22, Pb23, Pb3, Pb4, Pb5, Pb6, Pb7, Pb8, Pb9, Pin, PinMarker,
|
||||
Pa25, Pa26, Pa27, Pa28, Pa29, Pa30, Pa31, Pb0, Pb1, Pb2, Pb3, Pb4, Pb5, Pb6, Pb7, Pb8, Pb9,
|
||||
Pb10, Pb11, Pb12, Pb13, Pb14, Pb15, Pb16, Pb17, Pb18, Pb19, Pb22, Pb23, Pin,
|
||||
},
|
||||
sealed::Sealed,
|
||||
};
|
||||
|
||||
use super::{HwChipSelectId, SpiId};
|
||||
|
||||
pub trait PinSck: PinMarker {
|
||||
const SPI_ID: SpiId;
|
||||
const FUN_SEL: FunSel;
|
||||
}
|
||||
|
||||
pub trait PinMosi: PinMarker {
|
||||
const SPI_ID: SpiId;
|
||||
const FUN_SEL: FunSel;
|
||||
}
|
||||
|
||||
pub trait PinMiso: PinMarker {
|
||||
const SPI_ID: SpiId;
|
||||
const FUN_SEL: FunSel;
|
||||
}
|
||||
|
||||
pub trait HwCsProvider {
|
||||
const PIN_ID: PinId;
|
||||
const SPI_ID: SpiId;
|
||||
const FUN_SEL: FunSel;
|
||||
const CS_ID: HwChipSelectId;
|
||||
}
|
||||
use super::{Bank, HwChipSelectId};
|
||||
|
||||
macro_rules! hw_cs_pins {
|
||||
($SpiId:path, $(($Px:ident, $FunSel:path, $HwCsIdent:path),)+) => {
|
||||
$(
|
||||
impl HwCsProvider for Pin<$Px> {
|
||||
const PIN_ID: PinId = $Px::ID;
|
||||
const SPI_ID: SpiId = $SpiId;
|
||||
const SPI_ID: Bank = $SpiId;
|
||||
const FUN_SEL: FunSel = $FunSel;
|
||||
const CS_ID: HwChipSelectId = $HwCsIdent;
|
||||
}
|
||||
@@ -60,7 +39,7 @@ macro_rules! hw_cs_multi_pin {
|
||||
$cs_id:path
|
||||
) => {
|
||||
#[doc = concat!(
|
||||
"Newtype wrapper to use [Pin] [`", stringify!($pin_ty),
|
||||
"Newtype wrapper to use [Pin] [`", stringify!($pin_id),
|
||||
"`] as a HW CS pin for [`", stringify!($spi_id),
|
||||
"`] with [`", stringify!($cs_id), "`]."
|
||||
)]
|
||||
@@ -76,7 +55,7 @@ macro_rules! hw_cs_multi_pin {
|
||||
|
||||
impl HwCsProvider for $name {
|
||||
const PIN_ID: PinId = <$pin_id as PinIdProvider>::ID;
|
||||
const SPI_ID: SpiId = $spi_id;
|
||||
const SPI_ID: Bank = $spi_id;
|
||||
const FUN_SEL: FunSel = $fun_sel;
|
||||
const CS_ID: HwChipSelectId = $cs_id;
|
||||
}
|
||||
@@ -86,41 +65,33 @@ macro_rules! hw_cs_multi_pin {
|
||||
// SPIA
|
||||
|
||||
impl PinSck for Pin<Pa31> {
|
||||
const SPI_ID: SpiId = SpiId::A;
|
||||
const SPI_ID: Bank = Bank::Spi0;
|
||||
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||
}
|
||||
impl PinMosi for Pin<Pa30> {
|
||||
const SPI_ID: SpiId = SpiId::A;
|
||||
const SPI_ID: Bank = Bank::Spi0;
|
||||
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||
}
|
||||
impl PinMiso for Pin<Pa29> {
|
||||
const SPI_ID: SpiId = SpiId::A;
|
||||
const SPI_ID: Bank = Bank::Spi0;
|
||||
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||
}
|
||||
|
||||
pub type SpiAPortASck = Pin<Pa31>;
|
||||
pub type SpiAPortAMosi = Pin<Pa30>;
|
||||
pub type SpiAPortAMiso = Pin<Pa29>;
|
||||
|
||||
impl PinSck for Pin<Pb9> {
|
||||
const SPI_ID: SpiId = SpiId::A;
|
||||
const SPI_ID: Bank = Bank::Spi0;
|
||||
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||
}
|
||||
impl PinMosi for Pin<Pb8> {
|
||||
const SPI_ID: SpiId = SpiId::A;
|
||||
const SPI_ID: Bank = Bank::Spi0;
|
||||
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||
}
|
||||
impl PinMiso for Pin<Pb7> {
|
||||
const SPI_ID: SpiId = SpiId::A;
|
||||
const SPI_ID: Bank = Bank::Spi0;
|
||||
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||
}
|
||||
|
||||
pub type SpiAPortBSck = Pin<Pb9>;
|
||||
pub type SpiAPortBMosi = Pin<Pb8>;
|
||||
pub type SpiAPortBMiso = Pin<Pb7>;
|
||||
|
||||
hw_cs_pins!(
|
||||
SpiId::A,
|
||||
Bank::Spi0,
|
||||
(Pb0, FunSel::Sel2, HwChipSelectId::Id1),
|
||||
(Pb1, FunSel::Sel2, HwChipSelectId::Id2),
|
||||
(Pb2, FunSel::Sel2, HwChipSelectId::Id3),
|
||||
@@ -138,21 +109,21 @@ hw_cs_pins!(
|
||||
hw_cs_multi_pin!(
|
||||
PinPb0SpiaHwCsId1,
|
||||
Pb0,
|
||||
SpiId::A,
|
||||
Bank::Spi0,
|
||||
FunSel::Sel2,
|
||||
HwChipSelectId::Id1
|
||||
);
|
||||
hw_cs_multi_pin!(
|
||||
PinPb1SpiaHwCsId2,
|
||||
Pb1,
|
||||
SpiId::A,
|
||||
Bank::Spi0,
|
||||
FunSel::Sel2,
|
||||
HwChipSelectId::Id2
|
||||
);
|
||||
hw_cs_multi_pin!(
|
||||
PinPb2SpiaHwCsId3,
|
||||
Pb2,
|
||||
SpiId::A,
|
||||
Bank::Spi0,
|
||||
FunSel::Sel2,
|
||||
HwChipSelectId::Id3
|
||||
);
|
||||
@@ -160,21 +131,21 @@ hw_cs_multi_pin!(
|
||||
hw_cs_multi_pin!(
|
||||
PinPa21SpiaHwCsId7,
|
||||
Pa21,
|
||||
SpiId::A,
|
||||
Bank::Spi0,
|
||||
FunSel::Sel1,
|
||||
HwChipSelectId::Id7
|
||||
);
|
||||
hw_cs_multi_pin!(
|
||||
PinPa22SpiaHwCsId6,
|
||||
Pa22,
|
||||
SpiId::A,
|
||||
Bank::Spi0,
|
||||
FunSel::Sel1,
|
||||
HwChipSelectId::Id6
|
||||
);
|
||||
hw_cs_multi_pin!(
|
||||
PinPa23SpiaHwCsId5,
|
||||
Pa23,
|
||||
SpiId::A,
|
||||
Bank::Spi0,
|
||||
FunSel::Sel1,
|
||||
HwChipSelectId::Id5
|
||||
);
|
||||
@@ -182,15 +153,15 @@ hw_cs_multi_pin!(
|
||||
// SPIB
|
||||
|
||||
impl PinSck for Pin<Pa20> {
|
||||
const SPI_ID: SpiId = SpiId::B;
|
||||
const SPI_ID: Bank = Bank::Spi1;
|
||||
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||
}
|
||||
impl PinMosi for Pin<Pa19> {
|
||||
const SPI_ID: SpiId = SpiId::B;
|
||||
const SPI_ID: Bank = Bank::Spi1;
|
||||
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||
}
|
||||
impl PinMiso for Pin<Pa18> {
|
||||
const SPI_ID: SpiId = SpiId::B;
|
||||
const SPI_ID: Bank = Bank::Spi1;
|
||||
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||
}
|
||||
|
||||
@@ -199,34 +170,34 @@ pub type SpiBPortAMosi = Pin<Pa19>;
|
||||
pub type SpiBPortAMiso = Pin<Pa18>;
|
||||
|
||||
impl PinSck for Pin<Pb19> {
|
||||
const SPI_ID: SpiId = SpiId::B;
|
||||
const SPI_ID: Bank = Bank::Spi1;
|
||||
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||
}
|
||||
impl PinMosi for Pin<Pb18> {
|
||||
const SPI_ID: SpiId = SpiId::B;
|
||||
const SPI_ID: Bank = Bank::Spi1;
|
||||
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||
}
|
||||
impl PinMiso for Pin<Pb17> {
|
||||
const SPI_ID: SpiId = SpiId::B;
|
||||
const SPI_ID: Bank = Bank::Spi1;
|
||||
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||
}
|
||||
|
||||
impl PinSck for Pin<Pb5> {
|
||||
const SPI_ID: SpiId = SpiId::B;
|
||||
const SPI_ID: Bank = Bank::Spi1;
|
||||
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||
}
|
||||
impl PinMosi for Pin<Pb4> {
|
||||
const SPI_ID: SpiId = SpiId::B;
|
||||
const SPI_ID: Bank = Bank::Spi1;
|
||||
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||
}
|
||||
impl PinMiso for Pin<Pb3> {
|
||||
const SPI_ID: SpiId = SpiId::B;
|
||||
const SPI_ID: Bank = Bank::Spi1;
|
||||
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||
}
|
||||
|
||||
// TODO: Need to deal with these duplications..
|
||||
hw_cs_pins!(
|
||||
SpiId::B,
|
||||
Bank::Spi1,
|
||||
(Pb16, FunSel::Sel1, HwChipSelectId::Id0),
|
||||
(Pb15, FunSel::Sel1, HwChipSelectId::Id1),
|
||||
(Pb14, FunSel::Sel1, HwChipSelectId::Id2),
|
||||
@@ -247,21 +218,21 @@ hw_cs_pins!(
|
||||
hw_cs_multi_pin!(
|
||||
PinPb0SpibHwCsId2,
|
||||
Pb0,
|
||||
SpiId::B,
|
||||
Bank::Spi1,
|
||||
FunSel::Sel1,
|
||||
HwChipSelectId::Id2
|
||||
);
|
||||
hw_cs_multi_pin!(
|
||||
PinPb1SpibHwCsId1,
|
||||
Pb1,
|
||||
SpiId::B,
|
||||
Bank::Spi1,
|
||||
FunSel::Sel1,
|
||||
HwChipSelectId::Id1
|
||||
);
|
||||
hw_cs_multi_pin!(
|
||||
PinPb2SpibHwCsId0,
|
||||
Pb2,
|
||||
SpiId::B,
|
||||
Bank::Spi1,
|
||||
FunSel::Sel1,
|
||||
HwChipSelectId::Id0
|
||||
);
|
||||
@@ -269,21 +240,21 @@ hw_cs_multi_pin!(
|
||||
hw_cs_multi_pin!(
|
||||
PinPb10SpibHwCsId6,
|
||||
Pb10,
|
||||
SpiId::B,
|
||||
Bank::Spi1,
|
||||
FunSel::Sel1,
|
||||
HwChipSelectId::Id6
|
||||
);
|
||||
hw_cs_multi_pin!(
|
||||
PinPb11SpibHwCsId5,
|
||||
Pb11,
|
||||
SpiId::B,
|
||||
Bank::Spi1,
|
||||
FunSel::Sel1,
|
||||
HwChipSelectId::Id5
|
||||
);
|
||||
hw_cs_multi_pin!(
|
||||
PinPb12SpibHwCsId4,
|
||||
Pb12,
|
||||
SpiId::B,
|
||||
Bank::Spi1,
|
||||
FunSel::Sel1,
|
||||
HwChipSelectId::Id4
|
||||
);
|
||||
@@ -291,21 +262,21 @@ hw_cs_multi_pin!(
|
||||
hw_cs_multi_pin!(
|
||||
PinPb10SpibHwCsId2,
|
||||
Pb10,
|
||||
SpiId::B,
|
||||
Bank::Spi1,
|
||||
FunSel::Sel2,
|
||||
HwChipSelectId::Id2
|
||||
);
|
||||
hw_cs_multi_pin!(
|
||||
PinPb11SpibHwCsId1,
|
||||
Pb11,
|
||||
SpiId::B,
|
||||
Bank::Spi1,
|
||||
FunSel::Sel2,
|
||||
HwChipSelectId::Id1
|
||||
);
|
||||
hw_cs_multi_pin!(
|
||||
PinPb12SpibHwCsId0,
|
||||
Pb12,
|
||||
SpiId::B,
|
||||
Bank::Spi1,
|
||||
FunSel::Sel2,
|
||||
HwChipSelectId::Id0
|
||||
);
|
||||
@@ -313,60 +284,29 @@ hw_cs_multi_pin!(
|
||||
hw_cs_multi_pin!(
|
||||
PinPa21SpibHwCsId7,
|
||||
Pa21,
|
||||
SpiId::B,
|
||||
Bank::Spi1,
|
||||
FunSel::Sel2,
|
||||
HwChipSelectId::Id7
|
||||
);
|
||||
hw_cs_multi_pin!(
|
||||
PinPa22SpibHwCsId6,
|
||||
Pa22,
|
||||
SpiId::B,
|
||||
Bank::Spi1,
|
||||
FunSel::Sel2,
|
||||
HwChipSelectId::Id6
|
||||
);
|
||||
hw_cs_multi_pin!(
|
||||
PinPa23SpibHwCsId5,
|
||||
Pa23,
|
||||
SpiId::B,
|
||||
Bank::Spi1,
|
||||
FunSel::Sel2,
|
||||
HwChipSelectId::Id5
|
||||
);
|
||||
|
||||
// SPIC
|
||||
|
||||
/*
|
||||
// Dummy pin defintion for the ROM SCK.
|
||||
pub struct RomSck;
|
||||
// Dummy pin defintion for the ROM MOSI.
|
||||
pub struct RomMosi;
|
||||
// Dummy pin defintion for the ROM MISO.
|
||||
pub struct RomMiso;
|
||||
// Dummy pin defintion for the ROM chip select.
|
||||
pub struct RomCs;
|
||||
|
||||
impl Sealed for RomSck {}
|
||||
impl PinSck for RomSck {
|
||||
const SPI_ID: SpiId = SpiId::C;
|
||||
/// Function select does not make sense here, just select default value.
|
||||
const FUN_SEL: FunSel = FunSel::Sel0;
|
||||
}
|
||||
impl Sealed for RomMosi {}
|
||||
impl PinMosi for RomMosi {
|
||||
const SPI_ID: SpiId = SpiId::C;
|
||||
/// Function select does not make sense here, just select default value.
|
||||
const FUN_SEL: FunSel = FunSel::Sel0;
|
||||
}
|
||||
impl Sealed for RomMiso {}
|
||||
impl PinMiso for RomMiso {
|
||||
const SPI_ID: SpiId = SpiId::C;
|
||||
/// Function select does not make sense here, just select default value.
|
||||
const FUN_SEL: FunSel = FunSel::Sel0;
|
||||
}
|
||||
impl Sealed for RomCs {}
|
||||
*/
|
||||
|
||||
hw_cs_pins!(
|
||||
SpiId::C,
|
||||
Bank::Spi2,
|
||||
(Pb9, FunSel::Sel3, HwChipSelectId::Id1),
|
||||
(Pb8, FunSel::Sel3, HwChipSelectId::Id2),
|
||||
(Pb7, FunSel::Sel3, HwChipSelectId::Id3),
|
||||
@@ -380,21 +320,21 @@ hw_cs_pins!(
|
||||
hw_cs_multi_pin!(
|
||||
PinPa21SpicHwCsId3,
|
||||
Pa21,
|
||||
SpiId::C,
|
||||
Bank::Spi2,
|
||||
FunSel::Sel3,
|
||||
HwChipSelectId::Id3
|
||||
);
|
||||
hw_cs_multi_pin!(
|
||||
PinPa22SpicHwCsId2,
|
||||
Pa22,
|
||||
SpiId::C,
|
||||
Bank::Spi2,
|
||||
FunSel::Sel3,
|
||||
HwChipSelectId::Id2
|
||||
);
|
||||
hw_cs_multi_pin!(
|
||||
PinPa23SpicHwCsId1,
|
||||
Pa23,
|
||||
SpiId::C,
|
||||
Bank::Spi2,
|
||||
FunSel::Sel3,
|
||||
HwChipSelectId::Id1
|
||||
);
|
||||
@@ -402,23 +342,14 @@ hw_cs_multi_pin!(
|
||||
hw_cs_multi_pin!(
|
||||
PinPa20SpicHwCsId1,
|
||||
Pa20,
|
||||
SpiId::C,
|
||||
Bank::Spi2,
|
||||
FunSel::Sel1,
|
||||
HwChipSelectId::Id1
|
||||
);
|
||||
hw_cs_multi_pin!(
|
||||
PinPa20SpicHwCsId4,
|
||||
Pa20,
|
||||
SpiId::C,
|
||||
Bank::Spi2,
|
||||
FunSel::Sel3,
|
||||
HwChipSelectId::Id4
|
||||
);
|
||||
|
||||
/*
|
||||
impl HwCsProvider for RomCs {
|
||||
const CS_ID: HwChipSelectId = HwChipSelectId::Id0;
|
||||
const SPI_ID: SpiId = SpiId::C;
|
||||
/// Function select does not make sense here, just select default value.
|
||||
const FUN_SEL: FunSel = FunSel::Sel0;
|
||||
}
|
||||
*/
|
279
vorago-shared-periphs/src/spi/regs.rs
Normal file
279
vorago-shared-periphs/src/spi/regs.rs
Normal file
@@ -0,0 +1,279 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub use crate::shared::{FifoClear, TriggerLevel};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "vor1x")] {
|
||||
/// SPI A base address
|
||||
pub const BASE_ADDR_0: usize = 0x4005_0000;
|
||||
/// SPI B base address
|
||||
pub const BASE_ADDR_1: usize = 0x4005_1000;
|
||||
/// SPI C base address
|
||||
pub const BASE_ADDR_2: usize = 0x4005_2000;
|
||||
} else if #[cfg(feature = "vor4x")] {
|
||||
/// SPI 0 base address
|
||||
pub const BASE_ADDR_0: usize = 0x4001_5000;
|
||||
/// SPI 1 base address
|
||||
pub const BASE_ADDR_1: usize = 0x4001_5400;
|
||||
/// SPI 2 base address
|
||||
pub const BASE_ADDR_2: usize = 0x4001_5800;
|
||||
/// SPI 3 base address
|
||||
pub const BASE_ADDR_3: usize = 0x4001_5C00;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Bank {
|
||||
Spi0,
|
||||
Spi1,
|
||||
Spi2,
|
||||
#[cfg(feature = "vor4x")]
|
||||
Spi3,
|
||||
}
|
||||
|
||||
impl Bank {
|
||||
/// Unsafely steal the SPI peripheral block for the given port.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Circumvents ownership and safety guarantees by the HAL.
|
||||
pub unsafe fn steal_regs(&self) -> MmioSpi<'static> {
|
||||
Spi::new_mmio(*self)
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u4)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum WordSize {
|
||||
OneBit = 0x00,
|
||||
FourBits = 0x03,
|
||||
EightBits = 0x07,
|
||||
SixteenBits = 0x0f,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[bitbybit::bitenum(u3, exhaustive = true)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum HwChipSelectId {
|
||||
Id0 = 0,
|
||||
Id1 = 1,
|
||||
Id2 = 2,
|
||||
Id3 = 3,
|
||||
Id4 = 4,
|
||||
Id5 = 5,
|
||||
Id6 = 6,
|
||||
Id7 = 7,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
#[derive(Debug)]
|
||||
pub struct Control0 {
|
||||
#[bits(8..=15, rw)]
|
||||
scrdv: u8,
|
||||
#[bit(7, rw)]
|
||||
sph: bool,
|
||||
#[bit(6, rw)]
|
||||
spo: bool,
|
||||
#[bits(0..=3, rw)]
|
||||
word_size: Option<WordSize>,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
#[derive(Debug)]
|
||||
pub struct Control1 {
|
||||
#[bit(11, rw)]
|
||||
mtxpause: bool,
|
||||
#[bit(10, rw)]
|
||||
mdlycap: bool,
|
||||
#[bit(9, rw)]
|
||||
bm_stall: bool,
|
||||
#[bit(8, rw)]
|
||||
bm_start: bool,
|
||||
#[bit(7, rw)]
|
||||
blockmode: bool,
|
||||
#[bits(4..=6, rw)]
|
||||
ss: HwChipSelectId,
|
||||
#[bit(3, rw)]
|
||||
sod: bool,
|
||||
#[bit(2, rw)]
|
||||
slave_mode: bool,
|
||||
#[bit(1, rw)]
|
||||
enable: bool,
|
||||
#[bit(0, rw)]
|
||||
lbm: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct Data {
|
||||
/// Only used for BLOCKMODE. For received data, this bit indicated that the data was the first
|
||||
/// word after the chip select went active. For transmitted data, setting this bit to 1
|
||||
/// will end an SPI frame (deassert CS) after the specified data word.
|
||||
#[bit(31, rw)]
|
||||
bm_start_stop: bool,
|
||||
/// Only used for BLOCKMODE. Setting this bit to 1 along with the BMSTOP bit will end an SPI
|
||||
/// frame without any additional data to be transmitted. If BMSTOP is not set, this bit is
|
||||
/// ignored.
|
||||
#[bit(30, rw)]
|
||||
bm_skipdata: bool,
|
||||
#[bits(0..=15, rw)]
|
||||
data: u16,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct Status {
|
||||
/// TX FIFO below the trigger level.
|
||||
#[bit(7, r)]
|
||||
tx_trigger: bool,
|
||||
/// RX FIFO above or equals the trigger level.
|
||||
#[bit(6, r)]
|
||||
rx_trigger: bool,
|
||||
#[bit(5, r)]
|
||||
rx_data_first: bool,
|
||||
#[bit(4, r)]
|
||||
busy: bool,
|
||||
#[bit(3, r)]
|
||||
rx_full: bool,
|
||||
#[bit(2, r)]
|
||||
rx_not_empty: bool,
|
||||
#[bit(1, r)]
|
||||
tx_not_full: bool,
|
||||
#[bit(0, r)]
|
||||
tx_empty: bool,
|
||||
}
|
||||
|
||||
/// Clock divisor value. Bit 0 is ignored and always 0. This means that only the even values
|
||||
/// are used as clock divisor values, and uneven values are truncated to the next even value.
|
||||
/// A value of 0 acts as a 1 for the divisor value.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct ClkPrescaler(arbitrary_int::UInt<u32, 8>);
|
||||
|
||||
impl ClkPrescaler {
|
||||
pub const fn new(value: u8) -> Self {
|
||||
ClkPrescaler(arbitrary_int::UInt::<u32, 8>::new(value as u32))
|
||||
}
|
||||
pub const fn value(&self) -> u8 {
|
||||
self.0.value() as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct InterruptControl {
|
||||
/// TX FIFO count <= TX FIFO trigger level.
|
||||
#[bit(3, rw)]
|
||||
tx: bool,
|
||||
/// RX FIFO count >= RX FIFO trigger level.
|
||||
#[bit(2, rw)]
|
||||
rx: bool,
|
||||
/// Occurs when the RX FIFO has not been read within 32 clock ticks of the SPICLKx2 clock
|
||||
/// within the RX FIFO not being empty. Clearing the RX interrupt or reading data from the
|
||||
/// FIFO resets the timeout counter.
|
||||
#[bit(1, rw)]
|
||||
rx_timeout: bool,
|
||||
#[bit(0, rw)]
|
||||
rx_overrun: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct InterruptStatus {
|
||||
/// TX FIFO count <= TX FIFO trigger level.
|
||||
#[bit(3, r)]
|
||||
tx: bool,
|
||||
/// RX FIFO count >= RX FIFO trigger level.
|
||||
#[bit(2, r)]
|
||||
rx: bool,
|
||||
/// Occurs when the RX FIFO has not been read within 32 clock ticks of the SPICLKx2 clock
|
||||
/// within the RX FIFO not being empty. Clearing the RX interrupt or reading data from the
|
||||
/// FIFO resets the timeout counter.
|
||||
#[bit(1, r)]
|
||||
rx_timeout: bool,
|
||||
#[bit(0, r)]
|
||||
rx_overrun: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct InterruptClear {
|
||||
/// Clearing the RX interrupt or reading data from the FIFO resets the timeout counter.
|
||||
#[bit(1, w)]
|
||||
rx_timeout: bool,
|
||||
#[bit(0, w)]
|
||||
rx_overrun: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct State {
|
||||
#[bits(0..=7, r)]
|
||||
rx_state: u8,
|
||||
#[bits(8..=15, r)]
|
||||
rx_fifo: u8,
|
||||
#[bits(24..=31, r)]
|
||||
tx_fifo: u8,
|
||||
}
|
||||
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[mmio(no_ctors)]
|
||||
#[repr(C)]
|
||||
pub struct Spi {
|
||||
ctrl0: Control0,
|
||||
ctrl1: Control1,
|
||||
data: Data,
|
||||
#[mmio(PureRead)]
|
||||
status: Status,
|
||||
clkprescale: ClkPrescaler,
|
||||
irq_enb: InterruptControl,
|
||||
/// Raw interrupt status.
|
||||
#[mmio(PureRead)]
|
||||
irq_raw: InterruptStatus,
|
||||
/// Enabled interrupt status.
|
||||
#[mmio(PureRead)]
|
||||
irq_status: InterruptStatus,
|
||||
#[mmio(Write)]
|
||||
irq_clear: InterruptClear,
|
||||
rx_fifo_trigger: TriggerLevel,
|
||||
tx_fifo_trigger: TriggerLevel,
|
||||
#[mmio(Write)]
|
||||
fifo_clear: FifoClear,
|
||||
#[mmio(PureRead)]
|
||||
state: u32,
|
||||
#[cfg(feature = "vor1x")]
|
||||
_reserved: [u32; 0x3F2],
|
||||
#[cfg(feature = "vor4x")]
|
||||
_reserved: [u32; 0xF2],
|
||||
/// Vorago 1x: 0x0113_07E1. Vorago 4x: 0x0213_07E9.
|
||||
#[mmio(PureRead)]
|
||||
perid: u32,
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "vor1x")] {
|
||||
static_assertions::const_assert_eq!(core::mem::size_of::<Spi>(), 0x1000);
|
||||
} else if #[cfg(feature = "vor4x")] {
|
||||
static_assertions::const_assert_eq!(core::mem::size_of::<Spi>(), 0x400);
|
||||
}
|
||||
}
|
||||
|
||||
impl Spi {
|
||||
fn new_mmio_at(base: usize) -> MmioSpi<'static> {
|
||||
MmioSpi {
|
||||
ptr: base as *mut _,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_mmio(bank: Bank) -> MmioSpi<'static> {
|
||||
match bank {
|
||||
Bank::Spi0 => Self::new_mmio_at(BASE_ADDR_0),
|
||||
Bank::Spi1 => Self::new_mmio_at(BASE_ADDR_1),
|
||||
Bank::Spi2 => Self::new_mmio_at(BASE_ADDR_2),
|
||||
#[cfg(feature = "vor4x")]
|
||||
Bank::Spi3 => Self::new_mmio_at(BASE_ADDR_2),
|
||||
}
|
||||
}
|
||||
}
|
466
vorago-shared-periphs/src/timer/mod.rs
Normal file
466
vorago-shared-periphs/src/timer/mod.rs
Normal file
@@ -0,0 +1,466 @@
|
||||
pub mod regs;
|
||||
|
||||
use core::convert::Infallible;
|
||||
|
||||
#[cfg(feature = "vor1x")]
|
||||
pub use crate::InterruptConfig;
|
||||
pub use regs::{CascadeSource, InvalidTimerIndex, TimId};
|
||||
|
||||
use crate::{
|
||||
PeripheralSelect,
|
||||
gpio::{Pin, PinId, PinIdProvider},
|
||||
ioconfig::regs::FunSel,
|
||||
pins::PinMarker,
|
||||
sysconfig::enable_peripheral_clock,
|
||||
};
|
||||
use crate::{
|
||||
enable_nvic_interrupt,
|
||||
pins::{
|
||||
Pa0, Pa1, Pa2, Pa3, Pa4, Pa5, Pa6, Pa7, Pa8, Pa9, Pa10, Pa11, Pa12, Pa13, Pa14, Pa15, Pa24,
|
||||
Pa25, Pa26, Pa27, Pa28, Pa29, Pa30, Pa31, Pb0, Pb1, Pb2, Pb3, Pb4, Pb5, Pb6, Pb10, Pb11,
|
||||
Pb12, Pb13, Pb14, Pb15, Pb16, Pb17, Pb18, Pb19, Pb20, Pb21, Pb22, Pb23,
|
||||
},
|
||||
sealed::Sealed,
|
||||
time::Hertz,
|
||||
};
|
||||
use fugit::RateExtU32;
|
||||
|
||||
#[cfg(feature = "vor1x")]
|
||||
use va108xx as pac;
|
||||
#[cfg(feature = "vor4x")]
|
||||
use va416xx as pac;
|
||||
|
||||
//==================================================================================================
|
||||
// Defintions
|
||||
//==================================================================================================
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct CascadeControl {
|
||||
/// Enable Cascade 0 signal active as a requirement for counting
|
||||
pub enable_src_0: bool,
|
||||
/// Invert Cascade 0, making it active low
|
||||
pub inv_src_0: regs::CascadeInvert,
|
||||
/// Enable Cascade 1 signal active as a requirement for counting
|
||||
pub enable_src_1: bool,
|
||||
/// Invert Cascade 1, making it active low
|
||||
pub inv_src_1: regs::CascadeInvert,
|
||||
/// Specify required operation if both Cascade 0 and Cascade 1 are active.
|
||||
/// 0 is a logical AND of both cascade signals, 1 is a logical OR
|
||||
pub dual_operation: regs::DualCascadeOp,
|
||||
/// Enable trigger mode for Cascade 0. In trigger mode, couting will start with the selected
|
||||
/// cascade signal active, but once the counter is active, cascade control will be ignored
|
||||
pub trigger_mode_0: bool,
|
||||
/// Trigger mode, identical to [Self::trigger_mode_0] but for Cascade 1
|
||||
pub trigger_mode_1: bool,
|
||||
/// Enable Cascade 2 signal active as a requirement to stop counting. This mode is similar
|
||||
/// to the REQ_STOP control bit, but signalled by a Cascade source
|
||||
pub enable_stop_src_2: bool,
|
||||
/// Invert Cascade 2, making it active low
|
||||
pub inv_src_2: regs::CascadeInvert,
|
||||
/// The counter is automatically disabled if the corresponding Cascade 2 level-sensitive input
|
||||
/// souce is active when the count reaches 0. If the counter is not 0, the cascade control is
|
||||
/// ignored
|
||||
pub trigger_mode_2: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum CascadeSelect {
|
||||
Csd0 = 0,
|
||||
Csd1 = 1,
|
||||
Csd2 = 2,
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Valid TIM and PIN combinations
|
||||
//==================================================================================================
|
||||
|
||||
pub trait TimPin: PinMarker {
|
||||
const PIN_ID: PinId;
|
||||
const FUN_SEL: FunSel;
|
||||
const TIM_ID: TimId;
|
||||
}
|
||||
|
||||
pub trait TimMarker: Sealed {
|
||||
// TIM ID ranging from 0 to 23 for 24 TIM peripherals
|
||||
const ID: TimId;
|
||||
}
|
||||
|
||||
macro_rules! tim_marker {
|
||||
($TIMX:path, $ID:expr) => {
|
||||
impl TimMarker for $TIMX {
|
||||
const ID: TimId = TimId::new_unchecked($ID);
|
||||
}
|
||||
|
||||
impl Sealed for $TIMX {}
|
||||
};
|
||||
}
|
||||
|
||||
tim_marker!(pac::Tim0, 0);
|
||||
tim_marker!(pac::Tim1, 1);
|
||||
tim_marker!(pac::Tim2, 2);
|
||||
tim_marker!(pac::Tim3, 3);
|
||||
tim_marker!(pac::Tim4, 4);
|
||||
tim_marker!(pac::Tim5, 5);
|
||||
tim_marker!(pac::Tim6, 6);
|
||||
tim_marker!(pac::Tim7, 7);
|
||||
tim_marker!(pac::Tim8, 8);
|
||||
tim_marker!(pac::Tim9, 9);
|
||||
tim_marker!(pac::Tim10, 10);
|
||||
tim_marker!(pac::Tim11, 11);
|
||||
tim_marker!(pac::Tim12, 12);
|
||||
tim_marker!(pac::Tim13, 13);
|
||||
tim_marker!(pac::Tim14, 14);
|
||||
tim_marker!(pac::Tim15, 15);
|
||||
tim_marker!(pac::Tim16, 16);
|
||||
tim_marker!(pac::Tim17, 17);
|
||||
tim_marker!(pac::Tim18, 18);
|
||||
tim_marker!(pac::Tim19, 19);
|
||||
tim_marker!(pac::Tim20, 20);
|
||||
tim_marker!(pac::Tim21, 21);
|
||||
tim_marker!(pac::Tim22, 22);
|
||||
tim_marker!(pac::Tim23, 23);
|
||||
|
||||
pub trait ValidTimAndPin<Pin: TimPin, Tim: TimMarker>: Sealed {}
|
||||
|
||||
macro_rules! pin_and_tim {
|
||||
($Px:ident, $FunSel:path, $ID:expr) => {
|
||||
impl TimPin for Pin<$Px>
|
||||
where
|
||||
$Px: PinIdProvider,
|
||||
{
|
||||
const PIN_ID: PinId = $Px::ID;
|
||||
const FUN_SEL: FunSel = $FunSel;
|
||||
const TIM_ID: TimId = TimId::new_unchecked($ID);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pin_and_tim!(Pa0, FunSel::Sel1, 0);
|
||||
pin_and_tim!(Pa1, FunSel::Sel1, 1);
|
||||
pin_and_tim!(Pa2, FunSel::Sel1, 2);
|
||||
pin_and_tim!(Pa3, FunSel::Sel1, 3);
|
||||
pin_and_tim!(Pa4, FunSel::Sel1, 4);
|
||||
pin_and_tim!(Pa5, FunSel::Sel1, 5);
|
||||
pin_and_tim!(Pa6, FunSel::Sel1, 6);
|
||||
pin_and_tim!(Pa7, FunSel::Sel1, 7);
|
||||
pin_and_tim!(Pa8, FunSel::Sel1, 8);
|
||||
pin_and_tim!(Pa9, FunSel::Sel1, 9);
|
||||
pin_and_tim!(Pa10, FunSel::Sel1, 10);
|
||||
pin_and_tim!(Pa11, FunSel::Sel1, 11);
|
||||
pin_and_tim!(Pa12, FunSel::Sel1, 12);
|
||||
pin_and_tim!(Pa13, FunSel::Sel1, 13);
|
||||
pin_and_tim!(Pa14, FunSel::Sel1, 14);
|
||||
pin_and_tim!(Pa15, FunSel::Sel1, 15);
|
||||
|
||||
pin_and_tim!(Pa24, FunSel::Sel2, 16);
|
||||
pin_and_tim!(Pa25, FunSel::Sel2, 17);
|
||||
pin_and_tim!(Pa26, FunSel::Sel2, 18);
|
||||
pin_and_tim!(Pa27, FunSel::Sel2, 19);
|
||||
pin_and_tim!(Pa28, FunSel::Sel2, 20);
|
||||
pin_and_tim!(Pa29, FunSel::Sel2, 21);
|
||||
pin_and_tim!(Pa30, FunSel::Sel2, 22);
|
||||
pin_and_tim!(Pa31, FunSel::Sel2, 23);
|
||||
|
||||
pin_and_tim!(Pb0, FunSel::Sel3, 0);
|
||||
pin_and_tim!(Pb1, FunSel::Sel3, 1);
|
||||
pin_and_tim!(Pb2, FunSel::Sel3, 2);
|
||||
pin_and_tim!(Pb3, FunSel::Sel3, 3);
|
||||
pin_and_tim!(Pb4, FunSel::Sel3, 4);
|
||||
pin_and_tim!(Pb5, FunSel::Sel3, 5);
|
||||
pin_and_tim!(Pb6, FunSel::Sel3, 6);
|
||||
|
||||
pin_and_tim!(Pb10, FunSel::Sel3, 10);
|
||||
pin_and_tim!(Pb11, FunSel::Sel3, 11);
|
||||
pin_and_tim!(Pb12, FunSel::Sel3, 12);
|
||||
pin_and_tim!(Pb13, FunSel::Sel3, 13);
|
||||
pin_and_tim!(Pb14, FunSel::Sel3, 14);
|
||||
pin_and_tim!(Pb15, FunSel::Sel3, 15);
|
||||
pin_and_tim!(Pb16, FunSel::Sel3, 16);
|
||||
pin_and_tim!(Pb17, FunSel::Sel3, 17);
|
||||
pin_and_tim!(Pb18, FunSel::Sel3, 18);
|
||||
pin_and_tim!(Pb19, FunSel::Sel3, 19);
|
||||
pin_and_tim!(Pb20, FunSel::Sel3, 20);
|
||||
pin_and_tim!(Pb21, FunSel::Sel3, 21);
|
||||
pin_and_tim!(Pb22, FunSel::Sel3, 22);
|
||||
pin_and_tim!(Pb23, FunSel::Sel3, 23);
|
||||
|
||||
//==================================================================================================
|
||||
// Timers
|
||||
//==================================================================================================
|
||||
|
||||
/// Hardware timers
|
||||
pub struct CountdownTimer {
|
||||
id: TimId,
|
||||
regs: regs::MmioTimer<'static>,
|
||||
curr_freq: Hertz,
|
||||
sys_clk: Hertz,
|
||||
rst_val: u32,
|
||||
last_cnt: u32,
|
||||
}
|
||||
|
||||
impl CountdownTimer {
|
||||
/// Create a countdown timer structure for a given TIM peripheral.
|
||||
///
|
||||
/// This does not enable the timer. You can use the [Self::load], [Self::start],
|
||||
/// [Self::enable_interrupt] and [Self::enable] API to set up and configure the countdown
|
||||
/// timer.
|
||||
pub fn new<Tim: TimMarker>(sys_clk: Hertz, _tim: Tim) -> Self {
|
||||
enable_tim_clk(Tim::ID);
|
||||
assert_tim_reset_for_cycles(Tim::ID, 2);
|
||||
CountdownTimer {
|
||||
id: Tim::ID,
|
||||
regs: regs::Timer::new_mmio(Tim::ID),
|
||||
sys_clk,
|
||||
rst_val: 0,
|
||||
curr_freq: 0.Hz(),
|
||||
last_cnt: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn enable(&mut self) {
|
||||
self.regs
|
||||
.write_enable_control(regs::EnableControl::new_enable());
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn disable(&mut self) {
|
||||
self.regs
|
||||
.write_enable_control(regs::EnableControl::new_disable());
|
||||
}
|
||||
|
||||
pub fn enable_interrupt(&mut self, irq_cfg: InterruptConfig) {
|
||||
if irq_cfg.route {
|
||||
let irqsel = unsafe { pac::Irqsel::steal() };
|
||||
enable_peripheral_clock(PeripheralSelect::Irqsel);
|
||||
irqsel
|
||||
.tim0(self.id.value() as usize)
|
||||
.write(|w| unsafe { w.bits(irq_cfg.id as u32) });
|
||||
}
|
||||
if irq_cfg.enable_in_nvic {
|
||||
unsafe { enable_nvic_interrupt(irq_cfg.id) };
|
||||
}
|
||||
self.regs.modify_control(|mut value| {
|
||||
value.set_irq_enable(true);
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
/// This function only clears the interrupt enable bit.
|
||||
///
|
||||
/// It does not mask the interrupt in the NVIC or un-route the IRQ.
|
||||
#[inline(always)]
|
||||
pub fn disable_interrupt(&mut self) {
|
||||
self.regs.modify_control(|mut value| {
|
||||
value.set_irq_enable(false);
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
/// Calls [Self::load] to configure the specified frequency and then calls [Self::enable].
|
||||
pub fn start(&mut self, frequency: impl Into<Hertz>) {
|
||||
self.load(frequency);
|
||||
self.enable();
|
||||
}
|
||||
|
||||
/// Return `Ok` if the timer has wrapped. Peripheral will automatically clear the
|
||||
/// flag and restart the time if configured correctly
|
||||
pub fn wait(&mut self) -> nb::Result<(), Infallible> {
|
||||
let cnt = self.counter();
|
||||
if (cnt > self.last_cnt) || cnt == 0 {
|
||||
self.last_cnt = self.rst_val;
|
||||
Ok(())
|
||||
} else {
|
||||
self.last_cnt = cnt;
|
||||
Err(nb::Error::WouldBlock)
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the count down timer with a timeout but do not start it.
|
||||
pub fn load(&mut self, timeout: impl Into<Hertz>) {
|
||||
self.disable();
|
||||
self.curr_freq = timeout.into();
|
||||
self.rst_val = self.sys_clk.raw() / self.curr_freq.raw();
|
||||
self.set_reload(self.rst_val);
|
||||
self.set_count(self.rst_val);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set_reload(&mut self, val: u32) {
|
||||
self.regs.write_reset_value(val);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set_count(&mut self, val: u32) {
|
||||
self.regs.write_count_value(val);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn counter(&self) -> u32 {
|
||||
self.regs.read_count_value()
|
||||
}
|
||||
|
||||
/// Disable the counter, setting both enable and active bit to 0
|
||||
#[inline]
|
||||
pub fn auto_disable(&mut self, enable: bool) {
|
||||
self.regs.modify_control(|mut value| {
|
||||
value.set_auto_disable(enable);
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
/// This option only applies when the Auto-Disable functionality is 0.
|
||||
///
|
||||
/// The active bit is changed to 0 when count reaches 0, but the counter stays
|
||||
/// enabled. When Auto-Disable is 1, Auto-Deactivate is implied
|
||||
#[inline]
|
||||
pub fn auto_deactivate(&mut self, enable: bool) {
|
||||
self.regs.modify_control(|mut value| {
|
||||
value.set_auto_deactivate(enable);
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
/// Configure the cascade parameters
|
||||
pub fn cascade_control(&mut self, ctrl: CascadeControl) {
|
||||
self.regs.write_cascade_control(
|
||||
regs::CascadeControl::builder()
|
||||
.with_trigger2(ctrl.trigger_mode_2)
|
||||
.with_inv2(ctrl.inv_src_2)
|
||||
.with_en2(ctrl.enable_stop_src_2)
|
||||
.with_trigger1(ctrl.trigger_mode_1)
|
||||
.with_trigger0(ctrl.trigger_mode_0)
|
||||
.with_dual_cascade_op(ctrl.dual_operation)
|
||||
.with_inv1(ctrl.inv_src_1)
|
||||
.with_en1(ctrl.enable_src_1)
|
||||
.with_inv0(ctrl.inv_src_0)
|
||||
.with_en0(ctrl.enable_src_0)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn cascade_source(
|
||||
&mut self,
|
||||
cascade_index: CascadeSelect,
|
||||
src: regs::CascadeSource,
|
||||
) -> Result<(), regs::InvalidCascadeSourceId> {
|
||||
// Safety: Index range safe by enum values.
|
||||
unsafe {
|
||||
self.regs
|
||||
.write_cascade_unchecked(cascade_index as usize, regs::CascadeSourceReg::new(src)?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn curr_freq(&self) -> Hertz {
|
||||
self.curr_freq
|
||||
}
|
||||
|
||||
/// Disables the TIM and the dedicated TIM clock.
|
||||
pub fn stop_with_clock_disable(mut self) {
|
||||
self.disable();
|
||||
unsafe { va108xx::Sysconfig::steal() }
|
||||
.tim_clk_enable()
|
||||
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << self.id.value())) });
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Delay implementations
|
||||
//==================================================================================================
|
||||
//
|
||||
impl embedded_hal::delay::DelayNs for CountdownTimer {
|
||||
fn delay_ns(&mut self, ns: u32) {
|
||||
let ticks = (u64::from(ns)) * (u64::from(self.sys_clk.raw())) / 1_000_000_000;
|
||||
|
||||
let full_cycles = ticks >> 32;
|
||||
let mut last_count;
|
||||
let mut new_count;
|
||||
if full_cycles > 0 {
|
||||
self.set_reload(u32::MAX);
|
||||
self.set_count(u32::MAX);
|
||||
self.enable();
|
||||
|
||||
for _ in 0..full_cycles {
|
||||
// Always ensure that both values are the same at the start.
|
||||
new_count = self.counter();
|
||||
last_count = new_count;
|
||||
loop {
|
||||
new_count = self.counter();
|
||||
if new_count == 0 {
|
||||
// Wait till timer has wrapped.
|
||||
while self.counter() == 0 {
|
||||
cortex_m::asm::nop()
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Timer has definitely wrapped.
|
||||
if new_count > last_count {
|
||||
break;
|
||||
}
|
||||
last_count = new_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
let ticks = (ticks & u32::MAX as u64) as u32;
|
||||
self.disable();
|
||||
if ticks > 1 {
|
||||
self.set_reload(ticks);
|
||||
self.set_count(ticks);
|
||||
self.enable();
|
||||
last_count = ticks;
|
||||
|
||||
loop {
|
||||
new_count = self.counter();
|
||||
if new_count == 0 || (new_count > last_count) {
|
||||
break;
|
||||
}
|
||||
last_count = new_count;
|
||||
}
|
||||
}
|
||||
|
||||
self.disable();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn enable_tim_clk(id: TimId) {
|
||||
unsafe { pac::Sysconfig::steal() }
|
||||
.tim_clk_enable()
|
||||
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << id.value())) });
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn disable_tim_clk(id: TimId) {
|
||||
unsafe { pac::Sysconfig::steal() }
|
||||
.tim_clk_enable()
|
||||
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << (id.value()))) });
|
||||
}
|
||||
|
||||
/// Clear the reset bit of the TIM, holding it in reset
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Only the bit related to the corresponding TIM peripheral is modified
|
||||
#[inline]
|
||||
fn assert_tim_reset(id: TimId) {
|
||||
unsafe { pac::Peripherals::steal() }
|
||||
.sysconfig
|
||||
.tim_reset()
|
||||
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << id.value())) });
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn deassert_tim_reset(tim: TimId) {
|
||||
unsafe { pac::Peripherals::steal() }
|
||||
.sysconfig
|
||||
.tim_reset()
|
||||
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << tim.value())) });
|
||||
}
|
||||
|
||||
fn assert_tim_reset_for_cycles(tim: TimId, cycles: u32) {
|
||||
assert_tim_reset(tim);
|
||||
cortex_m::asm::delay(cycles);
|
||||
deassert_tim_reset(tim);
|
||||
}
|
0
vorago-shared-periphs/src/timer/pins_vor1x.rs
Normal file
0
vorago-shared-periphs/src/timer/pins_vor1x.rs
Normal file
309
vorago-shared-periphs/src/timer/regs.rs
Normal file
309
vorago-shared-periphs/src/timer/regs.rs
Normal file
@@ -0,0 +1,309 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use arbitrary_int::{Number, u7};
|
||||
|
||||
#[cfg(feature = "vor1x")]
|
||||
const BASE_ADDR: usize = 0x4002_0000;
|
||||
#[cfg(feature = "vor4x")]
|
||||
const BASE_ADDR: usize = 0x4001_8000;
|
||||
|
||||
#[bitbybit::bitenum(u3)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum StatusSelect {
|
||||
/// Pulse when timer reaches 0.
|
||||
OneCyclePulse = 0b000,
|
||||
OutputActiveBit = 0b001,
|
||||
/// Creates a divide by two output clock of the timer.
|
||||
ToggleOnEachCycle = 0b010,
|
||||
/// 1 when count value >= PWM A value, 0 otherwise
|
||||
PwmaOutput = 0b011,
|
||||
/// 1 when count value < PWM A value and >= PWM B, 0 when counter value >= PWM A value or < PWM
|
||||
/// B value
|
||||
PwmbOutput = 0b100,
|
||||
EnabledBit = 0b101,
|
||||
/// 1 when counter value <= PWM A value and 0 otherwise.
|
||||
PwmaActiveBit = 0b110,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct Control {
|
||||
/// The counter is requested to stop on the next normal count cycle.
|
||||
#[bit(9, rw)]
|
||||
request_stop: bool,
|
||||
#[bit(8, rw)]
|
||||
status_invert: bool,
|
||||
#[bits(5..=7, rw)]
|
||||
status_sel: Option<StatusSelect>,
|
||||
#[bit(4, rw)]
|
||||
irq_enable: bool,
|
||||
/// Only applies if the Auto-Disable bit is 0. The ACTIVE bit goes to 0 when the count reaches
|
||||
/// 0, but the timer remains enabled.
|
||||
#[bit(3, rw)]
|
||||
auto_deactivate: bool,
|
||||
/// Counter is fully disabled when count reaches 0, which means that both the ENABLE
|
||||
/// and ACTIVE bits go to 0.
|
||||
#[bit(2, rw)]
|
||||
auto_disable: bool,
|
||||
#[bit(1, r)]
|
||||
active: bool,
|
||||
#[bit(0, rw)]
|
||||
enable: bool,
|
||||
}
|
||||
|
||||
pub struct EnableControl(arbitrary_int::UInt<u32, 1>);
|
||||
|
||||
impl EnableControl {
|
||||
pub fn new_disable() -> Self {
|
||||
EnableControl(arbitrary_int::UInt::<u32, 1>::from_u32(0))
|
||||
}
|
||||
|
||||
pub fn new_enable() -> Self {
|
||||
EnableControl(arbitrary_int::UInt::<u32, 1>::from_u32(1))
|
||||
}
|
||||
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.0.value() != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum CascadeInvert {
|
||||
#[default]
|
||||
ActiveHigh = 0,
|
||||
ActiveLow = 1,
|
||||
}
|
||||
|
||||
/// When two cascade sources are selected, configure the required operation.
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum DualCascadeOp {
|
||||
#[default]
|
||||
LogicalAnd = 0,
|
||||
LogicalOr = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CascadeControl {
|
||||
/// The counter is automatically disabled if the corresponding Cascade 2 level-sensitive input
|
||||
/// souce is active when the count reaches 0. If the counter is not 0, the cascade control is
|
||||
/// ignored.
|
||||
#[bit(10, rw)]
|
||||
trigger2: bool,
|
||||
#[bit(9, rw)]
|
||||
inv2: CascadeInvert,
|
||||
/// Enable Cascade 2 signal active as a requirement to stop counting. This mode is similar
|
||||
/// to the REQ_STOP control bit, but signalled by a Cascade source.
|
||||
#[bit(8, rw)]
|
||||
en2: bool,
|
||||
/// Same as the trigger field for Cascade 0.
|
||||
#[bit(7, rw)]
|
||||
trigger1: bool,
|
||||
/// Enable trigger mode for Cascade 0. In trigger mode, couting will start with the selected
|
||||
/// cascade signal active, but once the counter is active, cascade control will be ignored.
|
||||
#[bit(6, rw)]
|
||||
trigger0: bool,
|
||||
/// Specify required operation if both Cascade 0 and Cascade 1 are active.
|
||||
/// 0 is a logical AND of both cascade signals, 1 is a logical OR.
|
||||
#[bit(4, rw)]
|
||||
dual_cascade_op: DualCascadeOp,
|
||||
/// Inversion bit for Cascade 1
|
||||
#[bit(3, rw)]
|
||||
inv1: CascadeInvert,
|
||||
/// Enable Cascade 1 signal active as a requirement for counting.
|
||||
#[bit(2, rw)]
|
||||
en1: bool,
|
||||
/// Inversion bit for Cascade 0.
|
||||
#[bit(1, rw)]
|
||||
inv0: CascadeInvert,
|
||||
/// Enable Cascade 0 signal active as a requirement for counting.
|
||||
#[bit(0, rw)]
|
||||
en0: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct InvalidCascadeSourceId;
|
||||
|
||||
/// The numbers are the base numbers for bundles like PORTA, PORTB or TIM
|
||||
#[cfg(feature = "vor1x")]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum CascadeSource {
|
||||
PortA(u8),
|
||||
PortB(u8),
|
||||
Tim(u8),
|
||||
RamSbe = 96,
|
||||
RamMbe = 97,
|
||||
RomSbe = 98,
|
||||
RomMbe = 99,
|
||||
Txev = 100,
|
||||
ClockDivider(u8),
|
||||
}
|
||||
|
||||
impl CascadeSource {
|
||||
#[cfg(feature = "vor1x")]
|
||||
pub fn id(&self) -> Result<u7, InvalidCascadeSourceId> {
|
||||
let port_check = |base: u8, id: u8, len: u8| -> Result<u7, InvalidCascadeSourceId> {
|
||||
if id > len - 1 {
|
||||
return Err(InvalidCascadeSourceId);
|
||||
}
|
||||
Ok(u7::new(base + id))
|
||||
};
|
||||
match self {
|
||||
CascadeSource::PortA(id) => port_check(0, *id, 32),
|
||||
CascadeSource::PortB(id) => port_check(32, *id, 32),
|
||||
CascadeSource::Tim(id) => port_check(64, *id, 24),
|
||||
CascadeSource::RamSbe => Ok(u7::new(96)),
|
||||
CascadeSource::RamMbe => Ok(u7::new(97)),
|
||||
CascadeSource::RomSbe => Ok(u7::new(98)),
|
||||
CascadeSource::RomMbe => Ok(u7::new(99)),
|
||||
CascadeSource::Txev => Ok(u7::new(100)),
|
||||
CascadeSource::ClockDivider(id) => port_check(120, *id, 8),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "vor1x")]
|
||||
pub fn from_raw(raw: u32) -> Result<Self, InvalidCascadeSourceId> {
|
||||
let id = u7::new((raw & 0x7F) as u8);
|
||||
if id.value() > 127 {
|
||||
return Err(InvalidCascadeSourceId);
|
||||
}
|
||||
let id = id.as_u8();
|
||||
if id < 32 {
|
||||
return Ok(CascadeSource::PortA(id));
|
||||
} else if (32..56).contains(&id) {
|
||||
return Ok(CascadeSource::PortB(id - 32));
|
||||
} else if (64..88).contains(&id) {
|
||||
return Ok(CascadeSource::Tim(id - 64));
|
||||
} else if id > 120 {
|
||||
return Ok(CascadeSource::ClockDivider(id - 120));
|
||||
}
|
||||
match id {
|
||||
96 => Ok(CascadeSource::RamSbe),
|
||||
97 => Ok(CascadeSource::RamMbe),
|
||||
98 => Ok(CascadeSource::RomSbe),
|
||||
99 => Ok(CascadeSource::RomMbe),
|
||||
100 => Ok(CascadeSource::Txev),
|
||||
_ => Err(InvalidCascadeSourceId),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct CascadeSourceReg {
|
||||
#[bits(0..=6, rw)]
|
||||
raw: u7,
|
||||
}
|
||||
|
||||
impl CascadeSourceReg {
|
||||
pub fn new(source: CascadeSource) -> Result<Self, InvalidCascadeSourceId> {
|
||||
let id = source.id()?;
|
||||
Ok(Self::new_with_raw_value(id.as_u32()))
|
||||
}
|
||||
|
||||
pub fn as_cascade_source(&self) -> Result<CascadeSource, InvalidCascadeSourceId> {
|
||||
CascadeSource::from_raw(self.raw().as_u32())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[mmio(no_ctors)]
|
||||
#[repr(C)]
|
||||
pub struct Timer {
|
||||
control: Control,
|
||||
reset_value: u32,
|
||||
count_value: u32,
|
||||
enable_control: EnableControl,
|
||||
cascade_control: CascadeControl,
|
||||
/// CASCADE0 and CASCADE1 are used to control the counting and activation of the counter.
|
||||
/// CASCADE2 is used to request stopping of the timer.
|
||||
cascade: [CascadeSourceReg; 3],
|
||||
/// PWM A compare value.
|
||||
pwma_value: u32,
|
||||
/// PWM B compare value.
|
||||
pwmb_value: u32,
|
||||
#[cfg(feature = "vor1x")]
|
||||
_reserved: [u32; 0x3f5],
|
||||
#[cfg(feature = "vor4x")]
|
||||
_reserved: [u32; 0xf5],
|
||||
/// Vorago 1x: 0x0111_07E1. Vorago 4x: 0x0211_07E9
|
||||
perid: u32,
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "vor1x")] {
|
||||
static_assertions::const_assert_eq!(core::mem::size_of::<Timer>(), 0x1000);
|
||||
} else if #[cfg(feature = "vor4x")] {
|
||||
static_assertions::const_assert_eq!(core::mem::size_of::<Timer>(), 0x400);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct InvalidTimerIndex(pub usize);
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct TimId(u8);
|
||||
|
||||
impl TimId {
|
||||
pub const fn new(index: usize) -> Result<Self, InvalidTimerIndex> {
|
||||
if index > 23 {
|
||||
return Err(InvalidTimerIndex(index));
|
||||
}
|
||||
Ok(TimId(index as u8))
|
||||
}
|
||||
|
||||
pub const fn new_unchecked(index: usize) -> Self {
|
||||
if index > 23 {
|
||||
panic!("invalid timer index");
|
||||
}
|
||||
TimId(index as u8)
|
||||
}
|
||||
|
||||
/// Unsafely steal the TIM peripheral block for the TIM ID.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Circumvents ownership and safety guarantees by the HAL.
|
||||
pub const unsafe fn steal_regs(&self) -> MmioTimer<'static> {
|
||||
Timer::new_mmio(*self)
|
||||
}
|
||||
|
||||
pub const fn value(&self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Timer {
|
||||
const fn new_mmio_at(base: usize) -> MmioTimer<'static> {
|
||||
MmioTimer {
|
||||
ptr: base as *mut _,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn new_mmio(id: TimId) -> MmioTimer<'static> {
|
||||
if cfg!(feature = "vor1x") {
|
||||
Timer::new_mmio_at(BASE_ADDR + 0x1000 * id.value() as usize)
|
||||
} else {
|
||||
Timer::new_mmio_at(BASE_ADDR + 0x400 * id.value() as usize)
|
||||
}
|
||||
}
|
||||
pub fn new_mmio_with_raw_index(
|
||||
timer_index: usize,
|
||||
) -> Result<MmioTimer<'static>, InvalidTimerIndex> {
|
||||
if timer_index > 23 {
|
||||
return Err(InvalidTimerIndex(timer_index));
|
||||
}
|
||||
if cfg!(feature = "vor1x") {
|
||||
Ok(Timer::new_mmio_at(BASE_ADDR + 0x1000 * timer_index))
|
||||
} else {
|
||||
Ok(Timer::new_mmio_at(BASE_ADDR + 0x400 * timer_index))
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,6 +2,22 @@ use core::marker::PhantomData;
|
||||
|
||||
use arbitrary_int::{u5, u6, u18};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "vor1x")] {
|
||||
/// UART A base address
|
||||
pub const BASE_ADDR_0: usize = 0x4004_0000;
|
||||
/// UART B base address
|
||||
pub const BASE_ADDR_1: usize = 0x4004_1000;
|
||||
} else if #[cfg(feature = "vor4x")] {
|
||||
/// UART 0 base address
|
||||
pub const BASE_ADDR_0: usize = 0x4002_4000;
|
||||
/// UART 1 base address
|
||||
pub const BASE_ADDR_1: usize = 0x4002_5000;
|
||||
/// UART 2 base address
|
||||
pub const BASE_ADDR_2: usize = 0x4001_7000;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Bank {
|
||||
@@ -21,21 +37,6 @@ impl Bank {
|
||||
Uart::new_mmio(*self)
|
||||
}
|
||||
}
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "vor1x")] {
|
||||
/// UART A base address
|
||||
const BASE_ADDR_0: usize = 0x4004_0000;
|
||||
/// UART B base address
|
||||
const BASE_ADDR_1: usize = 0x4004_1000;
|
||||
} else if #[cfg(feature = "vor4x")] {
|
||||
/// UART 0 base address
|
||||
const BASE_ADDR_0: usize = 0x4002_4000;
|
||||
/// UART 1 base address
|
||||
const BASE_ADDR_1: usize = 0x4002_5000;
|
||||
/// UART 2 base address
|
||||
const BASE_ADDR_2: usize = 0x4001_7000;
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
|
Reference in New Issue
Block a user