Closed xoviat closed 3 years ago
DAC and RTC are done. (For f3 and L4)
I plan to start with these, since they should be quick to knock out, and I can test them on current projects:
I think expecting the user to setup the pins correctly (eg check the user manual, and/or copy+paste examples from the examples folder) is worth avoiding this:
static SENSOR: Mutex<
RefCell<
Option<
Mlx9061x<
I2c<
I2C1,
(
PB6<Alternate<AF4, Output<OpenDrain>>>,
PB7<Alternate<AF4, Output<OpenDrain>>>,
),
>,
ic::Mlx90614,
>,
>,
>,
> = Mutex::new(RefCell::new(None));
In addition to this being verbose, it's not obvious where you find how to construct this; I've had to do it by trial+error with compile errors guiding the way.
The peripherals like I2C, SPI, DAC etc don't make use of the pins you pass as arguments directly; these args are just to check that you picked a valid pin, and set it to the right mode.
Could you help with the DMA? I haven't used it before, so don't have a grasp on where to start.
Would also like to start implementing and eventually hardware testing the current modules with H7 and L5.
GPIO, including EXTI. I plan to take a significant break from the existing approaches, by moving away from type state programming. It makes passing things across function boundaries, or declaring them statically tough. Thoughts? The downside is it won't catch misconfigured or wrong pins at compile time.
I can take a look at what your proposal is, but I am personally in favor of type-state programming.
It's a big departure from the norm, and may not be worth it. We can change later if it seems not worth it. Here are some thoughts, as they apply to GPIO and buses like I2C.
Big:
Small:
Untested idea: What if we don't use TST, but initialization functions for buses check that at least one set of pins is configured correctly using register reads?
Or... You pass an eum to bus init function. Eg I2cPins::Pb6Pb7
. The init fn handles pin setup. Leaning towards this.
So from the chat, the best approach might be to:
new<T>() -> Self
new_unchecked() -> Self
to all classes with new()
that require pinsfree()
from all classesAgree. Going to start with new_unchecked()
, since we can do this before making the GPIO module, and it's easier to implement cross-family.
With respect to DMA:
new_unchecked
: DmaConfig::TxRx => unsafe {
(*USART::ptr())
.cr3
.write(|w| w.dmar().enabled().dmat().enabled())
},
assert!(buffer.len() <= u16::max_value() as usize);
// The following configuration procedure is documented in the reference
// manual for STM32F75xxx and STM32F74xxx, section 8.3.18.
let nr = T::Stream::number();
// Disable stream
handle.dma.st[nr].cr.modify(|_, w| w.en().disabled());
while handle.dma.st[nr].cr.read().en().is_enabled() {}
T::Stream::clear_status_flags(&handle.dma);
// Set peripheral port register address
handle.dma.st[nr].par.write(|w| w.pa().bits(address));
// Set memory address
let memory_address = buffer.as_ptr() as u32;
handle.dma.st[nr]
.m0ar
.write(|w| w.m0a().bits(memory_address));
// Write number of data items to transfer
//
// We've asserted that `data.len()` fits into a `u16`, so the cast
// should be fine.
handle.dma.st[nr]
.ndtr
.write(|w| w.ndt().bits(buffer.len() as u16));
// Configure FIFO
handle.dma.st[nr].fcr.modify(|_, w| {
w
// Interrupt disabled
.feie()
.disabled()
// Direct mode enabled (FIFO disabled)
.dmdis()
.enabled()
});
// Select channel
handle.dma.st[nr].cr.write(|w| {
let w = T::Channel::select(w);
let w = match direction {
Direction::MemoryToPeripheral => w.dir().memory_to_peripheral(),
Direction::PeripheralToMemory => w.dir().peripheral_to_memory(),
};
w
// Single transfer
.mburst()
.single()
.pburst()
.single()
// Double-buffer mode disabled
.dbm()
.disabled()
// Very high priority
.pl()
.very_high()
// Memory data size
.msize()
.variant(Word::msize())
// Peripheral data size
.psize()
.variant(Word::psize())
// Memory increment mode
.minc()
.incremented()
// Peripheral increment mode
.pinc()
.fixed()
// Circular mode disabled
.circ()
.disabled()
// DMA is the flow controller
.pfctrl()
.dma()
// All interrupts disabled
.tcie()
.disabled()
.htie()
.disabled()
.teie()
.disabled()
.dmeie()
.disabled()
});
atomic::fence(Ordering::SeqCst);
handle.dma.st[T::Stream::number()]
.cr
.modify(|_, w| w.en().enabled());
This is the same across all stm32 devices, though there will be slight differences between them. The new devices have a more complex DMA which may need additional configuration. Also, note that although there is quite a bit of set up, it is nothing compared to the CPU time saved in communications compared to other methods.
new_unchecked
add for dac, i2c, and SPI.
Would you be willing to PR the DMA stuff once I have SPI, I2C, SERIAL, and ADC working on a few families?
Yes.
DMA xxx with nb::WouldBlock
It is completely impossible to implement nb
APIs with DMA.
Consider an API like this:
fn read(&mut self, bytes: &mut [u8]) -> nb::Result<(), Self::Error> { .. }
On first call, this would start the DMA read into bytes
, and then return WouldBlock
. The problem is after return, the bytes
buffer is no longer borrowed. The user could read from it which would race with DMA writes. Or even deallocate it, which would cause DMA to overwrite arbitrary memory.
Not true. See how it's done in the f7 HAL.
The f7 hal doesn't implement the embedded-hal nonblocking traits with DMA, does it?
Hey - DMA is over my head for now, so I don't imagine solving this issue in the near future. If someone knowledgeable with DMA want to give it a go, please do! (I'll get to QEI and CAN, also mentioned in this issue, sooner)
Going to tackle DMA and nonblocking, interrupt-based APIs, but don't plan to base it off of nb
. I'm going to check the above off as I add DMA for those peripherals. nb
will be used for the (non-DMA) embedded-hal
implementations only.
Could you provide more info on QEI? Can't find it in the RMs.
Re-open if you have info on QEI, or would like to tackle DMA2D.
Re QEI: It seems that on STM32s, QEIs are just a special mode for timers. See e.g. “29.4.18 Encoder interface mode” in https://www.st.com/resource/en/reference_manual/rm0440-stm32g4-series-advanced-armbased-32bit-mcus-stmicroelectronics.pdf.
This is a todo list of what features I'd like to see in this to consider using it; not that my opinion carries much weight, but it's a good place to start.
nb::WouldBlock API; interrupt and one-at-a-time API is simplest to implement but not usefulwith nb::WouldBlockwith nb::WouldBlockwith nb::WouldBlockThere are nice to have, but not critical:
IMO, DMA implementations should be the default and should be implemented first. They are the most difficult to implement, but also the most useful.