use stm32h7xx_hal as hal;
use mutex_trait::Mutex;
use super::design_parameters::{SampleBuffer, MAX_SAMPLE_BUFFER_SIZE};
use super::timers;
use core::convert::TryFrom;
use hal::{
dma::{
dma::{DMAReq, DmaConfig},
traits::TargetAddress,
DMAError, MemoryToPeripheral, Transfer,
},
spi::{HalDisabledSpi, HalEnabledSpi, HalSpi},
};
#[link_section = ".axisram.buffers"]
static mut DAC_BUF: [[SampleBuffer; 2]; 2] =
[[[0; MAX_SAMPLE_BUFFER_SIZE]; 2]; 2];
#[derive(Copy, Clone, Default)]
pub struct DacCode(pub u16);
impl DacCode {
pub const FULL_SCALE: f32 = 4.096 * 2.5;
pub const VOLT_PER_LSB: f32 = -Self::FULL_SCALE / i16::MIN as f32;
pub const LSB_PER_VOLT: f32 = 1. / Self::VOLT_PER_LSB;
}
impl TryFrom<f32> for DacCode {
type Error = ();
fn try_from(voltage: f32) -> Result<DacCode, ()> {
let code = voltage * Self::LSB_PER_VOLT;
if !(i16::MIN as f32..=i16::MAX as f32).contains(&code) {
Err(())
} else {
Ok(DacCode::from(code as i16))
}
}
}
impl From<DacCode> for f32 {
fn from(code: DacCode) -> f32 {
i16::from(code) as f32 * DacCode::VOLT_PER_LSB
}
}
impl From<DacCode> for i16 {
fn from(code: DacCode) -> i16 {
(code.0 as i16).wrapping_sub(i16::MIN)
}
}
impl From<i16> for DacCode {
fn from(value: i16) -> Self {
Self(value.wrapping_add(i16::MIN) as u16)
}
}
impl From<u16> for DacCode {
fn from(value: u16) -> Self {
Self(value)
}
}
macro_rules! dac_output {
($name:ident, $index:literal, $data_stream:ident,
$spi:ident, $trigger_channel:ident, $dma_req:ident) => {
struct $spi {
spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
_channel: timers::tim2::$trigger_channel,
}
impl $spi {
pub fn new(
_channel: timers::tim2::$trigger_channel,
spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Disabled, u16>,
) -> Self {
Self { spi, _channel }
}
pub fn start_dma(&mut self) {
self.spi.enable_dma_tx();
self.spi.inner().cr1.modify(|_, w| w.spe().set_bit());
self.spi.inner().cr1.modify(|_, w| w.cstart().started());
}
}
unsafe impl TargetAddress<MemoryToPeripheral> for $spi {
type MemSize = u16;
const REQUEST_LINE: Option<u8> = Some(DMAReq::$dma_req as u8);
fn address(&self) -> usize {
&self.spi.inner().txdr as *const _ as usize
}
}
pub struct $name {
// Note: SPI TX functionality may not be used from this structure to ensure safety with DMA.
transfer: Transfer<
hal::dma::dma::$data_stream<hal::stm32::DMA1>,
$spi,
MemoryToPeripheral,
&'static mut [u16],
hal::dma::DBTransfer,
>,
}
impl $name {
pub fn new(
spi: hal::spi::Spi<hal::stm32::$spi, hal::spi::Enabled, u16>,
stream: hal::dma::dma::$data_stream<hal::stm32::DMA1>,
trigger_channel: timers::tim2::$trigger_channel,
batch_size: usize,
) -> Self {
trigger_channel.listen_dma();
trigger_channel.to_output_compare(4 + $index);
let trigger_config = DmaConfig::default()
.memory_increment(true)
.double_buffer(true)
.peripheral_increment(false);
let mut spi = spi.disable();
spi.listen(hal::spi::Event::Error);
for buf in unsafe { DAC_BUF[$index].iter_mut() } {
for byte in buf.iter_mut() {
*byte = DacCode::try_from(0.0f32).unwrap().0;
}
}
let transfer: Transfer<_, _, MemoryToPeripheral, _, _> =
Transfer::init(
stream,
$spi::new(trigger_channel, spi),
unsafe { &mut DAC_BUF[$index][0][..batch_size] },
unsafe { Some(&mut DAC_BUF[$index][1][..batch_size]) },
trigger_config,
);
Self { transfer }
}
pub fn start(&mut self) {
self.transfer.start(|spi| spi.start_dma());
}
pub fn with_buffer<F, R>(&mut self, f: F) -> Result<R, DMAError>
where
F: FnOnce(&mut &'static mut [u16]) -> R,
{
unsafe {
self.transfer.next_dbm_transfer_with(|buf, _current| f(buf))
}
}
}
impl Mutex for $name {
type Data = &'static mut [u16];
fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R {
self.with_buffer(f).unwrap()
}
}
};
}
dac_output!(Dac0Output, 0, Stream6, SPI4, Channel3, Tim2Ch3);
dac_output!(Dac1Output, 1, Stream7, SPI5, Channel4, Tim2Ch4);