Merge branch 'main' into h7rs-xspi-fixes

This commit is contained in:
Rick Rogers 2025-07-25 11:34:21 -04:00
commit 33fc82a455
No known key found for this signature in database
GPG Key ID: 81A2B5FD9B4E4436
14 changed files with 508 additions and 87 deletions

2
.github/ci/book.sh vendored
View File

@ -1,5 +1,7 @@
#!/bin/bash
## on push branch=main
## priority -9
## dedup dequeue
set -euxo pipefail

2
.github/ci/doc.sh vendored
View File

@ -1,5 +1,7 @@
#!/bin/bash
## on push branch=main
## priority -10
## dedup dequeue
set -euxo pipefail

2
ci.sh
View File

@ -40,6 +40,8 @@ cargo batch \
--- build --release --manifest-path embassy-executor/Cargo.toml --target armv7r-none-eabihf --features arch-cortex-ar,executor-thread \
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32 \
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread \
--- build --release --manifest-path embassy-embedded-hal/Cargo.toml --target thumbv7em-none-eabi \
--- build --release --manifest-path embassy-embedded-hal/Cargo.toml --target thumbv7em-none-eabi --features time \
--- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features defmt \
--- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features defmt,defmt-timestamp-uptime,mock-driver \
--- build --release --manifest-path embassy-time-queue-utils/Cargo.toml --target thumbv6m-none-eabi \

View File

@ -8,7 +8,7 @@ The application we'll write is a simple 'push button, blink led' application, wh
== PAC version
The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provides distinct types to make accessing peripheral registers easier, but it does not prevent you from writing unsafe code.
The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provides distinct types to make accessing peripheral registers easier, but it does little to prevent you from configuring or coordinating those registers incorrectly.
Writing an application using the PAC directly is therefore not recommended, but if the functionality you want to use is not exposed in the upper layers, that's what you need to use.

View File

@ -19,7 +19,6 @@ target = "x86_64-unknown-linux-gnu"
[features]
time = ["dep:embassy-time"]
default = ["time"]
[dependencies]
embassy-hal-internal = { version = "0.3.0", path = "../embassy-hal-internal" }

View File

@ -1,5 +1,3 @@
use embedded_hal_02::blocking;
/// Wrapper that implements async traits using blocking implementations.
///
/// This allows driver writers to depend on the async traits while still supporting embedded-hal peripheral implementations.
@ -24,7 +22,7 @@ impl<T> BlockingAsync<T> {
impl<T, E> embedded_hal_1::i2c::ErrorType for BlockingAsync<T>
where
E: embedded_hal_1::i2c::Error + 'static,
T: blocking::i2c::WriteRead<Error = E> + blocking::i2c::Read<Error = E> + blocking::i2c::Write<Error = E>,
T: embedded_hal_1::i2c::I2c<Error = E>,
{
type Error = E;
}
@ -32,7 +30,7 @@ where
impl<T, E> embedded_hal_async::i2c::I2c for BlockingAsync<T>
where
E: embedded_hal_1::i2c::Error + 'static,
T: blocking::i2c::WriteRead<Error = E> + blocking::i2c::Read<Error = E> + blocking::i2c::Write<Error = E>,
T: embedded_hal_1::i2c::I2c<Error = E>,
{
async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
self.wrapped.read(address, read)
@ -51,9 +49,7 @@ where
address: u8,
operations: &mut [embedded_hal_1::i2c::Operation<'_>],
) -> Result<(), Self::Error> {
let _ = address;
let _ = operations;
todo!()
self.wrapped.transaction(address, operations)
}
}
@ -63,16 +59,16 @@ where
impl<T, E> embedded_hal_async::spi::ErrorType for BlockingAsync<T>
where
E: embedded_hal_1::spi::Error,
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
E: embedded_hal_async::spi::Error,
T: embedded_hal_1::spi::SpiBus<Error = E>,
{
type Error = E;
}
impl<T, E> embedded_hal_async::spi::SpiBus<u8> for BlockingAsync<T>
where
E: embedded_hal_1::spi::Error + 'static,
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
E: embedded_hal_async::spi::Error,
T: embedded_hal_1::spi::SpiBus<Error = E>,
{
async fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
@ -84,21 +80,17 @@ where
}
async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
self.wrapped.transfer(data)?;
self.wrapped.read(data)?;
Ok(())
}
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
// Ensure we write the expected bytes
for i in 0..core::cmp::min(read.len(), write.len()) {
read[i] = write[i].clone();
}
self.wrapped.transfer(read)?;
self.wrapped.transfer(read, write)?;
Ok(())
}
async fn transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
self.wrapped.transfer(data)?;
self.wrapped.transfer_in_place(data)?;
Ok(())
}
}

View File

@ -569,7 +569,7 @@ unsafe fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> {
unsafe {
core.MPU.ctrl.write(5); // enable mpu with background default map
core.MPU.rbar.write(stack_bottom as u32 & !0xff); // set address
core.MPU.rlar.write(1); // enable region
core.MPU.rlar.write(((stack_bottom as usize + 255) as u32) | 1);
}
Ok(())
}

View File

@ -129,6 +129,7 @@ defmt = [
"embassy-net-driver/defmt",
"embassy-time?/defmt",
"embassy-usb-synopsys-otg/defmt",
"stm32-metapac/defmt"
]
exti = []

View File

@ -403,6 +403,46 @@ impl<'d, T: Instance> Dac<'d, T, Async> {
Mode::NormalExternalBuffered,
)
}
/// Create a new `Dac` instance with external output pins and unbuffered mode.
///
/// This function consumes the underlying DAC peripheral and allows access to both channels.
/// The channels are configured for external output with the buffer disabled.
///
/// The channels are enabled on creation and begin to drive their output pins.
/// Note that some methods, such as `set_trigger()` and `set_mode()`, will
/// disable the channel; you must re-enable them with `enable()`.
///
/// By default, triggering is disabled, but it can be enabled using the `set_trigger()`
/// method on the underlying channels.
///
/// # Arguments
///
/// * `peri` - The DAC peripheral instance.
/// * `dma_ch1` - The DMA channel for DAC channel 1.
/// * `dma_ch2` - The DMA channel for DAC channel 2.
/// * `pin_ch1` - The GPIO pin for DAC channel 1 output.
/// * `pin_ch2` - The GPIO pin for DAC channel 2 output.
///
/// # Returns
///
/// A new `Dac` instance in unbuffered mode.
pub fn new_unbuffered(
peri: Peri<'d, T>,
dma_ch1: Peri<'d, impl Dma<T, Ch1>>,
dma_ch2: Peri<'d, impl Dma<T, Ch2>>,
pin_ch1: Peri<'d, impl DacPin<T, Ch1> + crate::gpio::Pin>,
pin_ch2: Peri<'d, impl DacPin<T, Ch2> + crate::gpio::Pin>,
) -> Self {
pin_ch1.set_as_analog();
pin_ch2.set_as_analog();
Self::new_inner(
peri,
new_dma!(dma_ch1),
new_dma!(dma_ch2),
#[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))]
Mode::NormalExternalUnbuffered,
)
}
/// Create a new `Dac` instance where the external output pins are not used,
/// so the DAC can only be used to generate internal signals but the GPIO

View File

@ -36,11 +36,46 @@ impl Address {
}
}
enum ReceiveResult {
DataAvailable,
StopReceived,
NewStart,
}
fn debug_print_interrupts(isr: stm32_metapac::i2c::regs::Isr) {
if isr.tcr() {
trace!("interrupt: tcr");
}
if isr.tc() {
trace!("interrupt: tc");
}
if isr.addr() {
trace!("interrupt: addr");
}
if isr.stopf() {
trace!("interrupt: stopf");
}
if isr.nackf() {
trace!("interrupt: nackf");
}
if isr.berr() {
trace!("interrupt: berr");
}
if isr.arlo() {
trace!("interrupt: arlo");
}
if isr.ovr() {
trace!("interrupt: ovr");
}
}
pub(crate) unsafe fn on_interrupt<T: Instance>() {
let regs = T::info().regs;
let isr = regs.isr().read();
if isr.tcr() || isr.tc() || isr.addr() || isr.stopf() || isr.nackf() || isr.berr() || isr.arlo() || isr.ovr() {
debug_print_interrupts(isr);
T::state().waker.wake();
}
@ -193,49 +228,132 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> {
fn flush_txdr(&self) {
if self.info.regs.isr().read().txis() {
self.info.regs.txdr().write(|w| w.set_txdata(0));
trace!("Flush TXDATA with zeroes");
self.info.regs.txdr().modify(|w| w.set_txdata(0));
}
if !self.info.regs.isr().read().txe() {
trace!("Flush TXDR");
self.info.regs.isr().modify(|w| w.set_txe(true))
}
}
fn wait_txe(&self, timeout: Timeout) -> Result<(), Error> {
fn error_occurred(&self, isr: &i2c::regs::Isr, timeout: Timeout) -> Result<(), Error> {
if isr.nackf() {
trace!("NACK triggered.");
self.info.regs.icr().modify(|reg| reg.set_nackcf(true));
// NACK should be followed by STOP
if let Ok(()) = self.wait_stop(timeout) {
trace!("Got STOP after NACK, clearing flag.");
self.info.regs.icr().modify(|reg| reg.set_stopcf(true));
}
self.flush_txdr();
return Err(Error::Nack);
} else if isr.berr() {
trace!("BERR triggered.");
self.info.regs.icr().modify(|reg| reg.set_berrcf(true));
self.flush_txdr();
return Err(Error::Bus);
} else if isr.arlo() {
trace!("ARLO triggered.");
self.info.regs.icr().modify(|reg| reg.set_arlocf(true));
self.flush_txdr();
return Err(Error::Arbitration);
} else if isr.ovr() {
trace!("OVR triggered.");
self.info.regs.icr().modify(|reg| reg.set_ovrcf(true));
return Err(Error::Overrun);
}
return Ok(());
}
fn wait_txis(&self, timeout: Timeout) -> Result<(), Error> {
let mut first_loop = true;
loop {
let isr = self.info.regs.isr().read();
if isr.txe() {
self.error_occurred(&isr, timeout)?;
if isr.txis() {
trace!("TXIS");
return Ok(());
} else if isr.berr() {
self.info.regs.icr().write(|reg| reg.set_berrcf(true));
return Err(Error::Bus);
} else if isr.arlo() {
self.info.regs.icr().write(|reg| reg.set_arlocf(true));
return Err(Error::Arbitration);
} else if isr.nackf() {
self.info.regs.icr().write(|reg| reg.set_nackcf(true));
self.flush_txdr();
return Err(Error::Nack);
}
{
if first_loop {
trace!("Waiting for TXIS...");
first_loop = false;
}
}
timeout.check()?;
}
}
fn wait_rxne(&self, timeout: Timeout) -> Result<(), Error> {
fn wait_stop_or_err(&self, timeout: Timeout) -> Result<(), Error> {
loop {
let isr = self.info.regs.isr().read();
if isr.rxne() {
self.error_occurred(&isr, timeout)?;
if isr.stopf() {
trace!("STOP triggered.");
self.info.regs.icr().modify(|reg| reg.set_stopcf(true));
return Ok(());
} else if isr.berr() {
self.info.regs.icr().write(|reg| reg.set_berrcf(true));
return Err(Error::Bus);
} else if isr.arlo() {
self.info.regs.icr().write(|reg| reg.set_arlocf(true));
return Err(Error::Arbitration);
} else if isr.nackf() {
self.info.regs.icr().write(|reg| reg.set_nackcf(true));
self.flush_txdr();
return Err(Error::Nack);
}
timeout.check()?;
}
}
fn wait_stop(&self, timeout: Timeout) -> Result<(), Error> {
loop {
let isr = self.info.regs.isr().read();
if isr.stopf() {
trace!("STOP triggered.");
self.info.regs.icr().modify(|reg| reg.set_stopcf(true));
return Ok(());
}
timeout.check()?;
}
}
fn wait_af(&self, timeout: Timeout) -> Result<(), Error> {
loop {
let isr = self.info.regs.isr().read();
if isr.nackf() {
trace!("AF triggered.");
self.info.regs.icr().modify(|reg| reg.set_nackcf(true));
return Ok(());
}
timeout.check()?;
}
}
fn wait_rxne(&self, timeout: Timeout) -> Result<ReceiveResult, Error> {
let mut first_loop = true;
loop {
let isr = self.info.regs.isr().read();
self.error_occurred(&isr, timeout)?;
if isr.stopf() {
trace!("STOP when waiting for RXNE.");
if self.info.regs.isr().read().rxne() {
trace!("Data received with STOP.");
return Ok(ReceiveResult::DataAvailable);
}
trace!("STOP triggered without data.");
return Ok(ReceiveResult::StopReceived);
} else if isr.rxne() {
trace!("RXNE.");
return Ok(ReceiveResult::DataAvailable);
} else if isr.addr() {
// Another addr event received, which means START was sent again
// which happens when accessing memory registers (common i2c interface design)
// e.g. master sends: START, write 1 byte (register index), START, read N bytes (until NACK)
// Possible to receive this flag at the same time as rxne, so check rxne first
trace!("START when waiting for RXNE. Ending receive loop.");
// Return without clearing ADDR so `listen` can catch it
return Ok(ReceiveResult::NewStart);
}
{
if first_loop {
trace!("Waiting for RXNE...");
first_loop = false;
}
}
timeout.check()?;
@ -245,20 +363,10 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> {
fn wait_tc(&self, timeout: Timeout) -> Result<(), Error> {
loop {
let isr = self.info.regs.isr().read();
self.error_occurred(&isr, timeout)?;
if isr.tc() {
return Ok(());
} else if isr.berr() {
self.info.regs.icr().write(|reg| reg.set_berrcf(true));
return Err(Error::Bus);
} else if isr.arlo() {
self.info.regs.icr().write(|reg| reg.set_arlocf(true));
return Err(Error::Arbitration);
} else if isr.nackf() {
self.info.regs.icr().write(|reg| reg.set_nackcf(true));
self.flush_txdr();
return Err(Error::Nack);
}
timeout.check()?;
}
}
@ -344,7 +452,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> {
// Wait until we are allowed to send data
// (START has been ACKed or last byte when
// through)
if let Err(err) = self.wait_txe(timeout) {
if let Err(err) = self.wait_txis(timeout) {
if send_stop {
self.master_stop();
}
@ -459,7 +567,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> {
// Wait until we are allowed to send data
// (START has been ACKed or last byte when
// through)
if let Err(err) = self.wait_txe(timeout) {
if let Err(err) = self.wait_txis(timeout) {
self.master_stop();
return Err(err);
}
@ -884,10 +992,11 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> {
// clear the address flag, will stop the clock stretching.
// this should only be done after the dma transfer has been set up.
info.regs.icr().modify(|reg| reg.set_addrcf(true));
trace!("ADDRCF cleared (ADDR interrupt enabled, clock stretching ended)");
}
// A blocking read operation
fn slave_read_internal(&self, read: &mut [u8], timeout: Timeout) -> Result<(), Error> {
fn slave_read_internal(&self, read: &mut [u8], timeout: Timeout) -> Result<usize, Error> {
let completed_chunks = read.len() / 255;
let total_chunks = if completed_chunks * 255 == read.len() {
completed_chunks
@ -895,20 +1004,46 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> {
completed_chunks + 1
};
let last_chunk_idx = total_chunks.saturating_sub(1);
let total_len = read.len();
let mut remaining_len = total_len;
for (number, chunk) in read.chunks_mut(255).enumerate() {
if number != 0 {
trace!(
"--- Slave RX transmission start - chunk: {}, expected (max) size: {}",
number,
chunk.len()
);
if number == 0 {
Self::slave_start(self.info, chunk.len(), number != last_chunk_idx);
} else {
Self::reload(self.info, chunk.len(), number != last_chunk_idx, timeout)?;
}
let mut index = 0;
for byte in chunk {
// Wait until we have received something
self.wait_rxne(timeout)?;
*byte = self.info.regs.rxdr().read().rxdata();
match self.wait_rxne(timeout) {
Ok(ReceiveResult::StopReceived) | Ok(ReceiveResult::NewStart) => {
trace!("--- Slave RX transmission end (early)");
return Ok(total_len - remaining_len); // Return N bytes read
}
Ok(ReceiveResult::DataAvailable) => {
*byte = self.info.regs.rxdr().read().rxdata();
remaining_len = remaining_len.saturating_sub(1);
{
trace!("Slave RX data {}: {:#04x}", index, byte);
index = index + 1;
}
}
Err(e) => return Err(e),
};
}
}
self.wait_stop_or_err(timeout)?;
Ok(())
trace!("--- Slave RX transmission end");
Ok(total_len - remaining_len) // Return N bytes read
}
// A blocking write operation
@ -922,19 +1057,36 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> {
let last_chunk_idx = total_chunks.saturating_sub(1);
for (number, chunk) in write.chunks(255).enumerate() {
if number != 0 {
trace!(
"--- Slave TX transmission start - chunk: {}, size: {}",
number,
chunk.len()
);
if number == 0 {
Self::slave_start(self.info, chunk.len(), number != last_chunk_idx);
} else {
Self::reload(self.info, chunk.len(), number != last_chunk_idx, timeout)?;
}
let mut index = 0;
for byte in chunk {
// Wait until we are allowed to send data
// (START has been ACKed or last byte when
// through)
self.wait_txe(timeout)?;
// (START has been ACKed or last byte when through)
self.wait_txis(timeout)?;
{
trace!("Slave TX data {}: {:#04x}", index, byte);
index = index + 1;
}
self.info.regs.txdr().write(|w| w.set_txdata(*byte));
}
}
self.wait_af(timeout)?;
self.flush_txdr();
self.wait_stop_or_err(timeout)?;
trace!("--- Slave TX transmission end");
Ok(())
}
@ -945,6 +1097,7 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> {
let state = self.state;
self.info.regs.cr1().modify(|reg| {
reg.set_addrie(true);
trace!("Enable ADDRIE");
});
poll_fn(|cx| {
@ -953,17 +1106,24 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> {
if !isr.addr() {
Poll::Pending
} else {
trace!("ADDR triggered (address match)");
// we do not clear the address flag here as it will be cleared by the dma read/write
// if we clear it here the clock stretching will stop and the master will read in data before the slave is ready to send it
match isr.dir() {
i2c::vals::Dir::WRITE => Poll::Ready(Ok(SlaveCommand {
kind: SlaveCommandKind::Write,
address: self.determine_matched_address()?,
})),
i2c::vals::Dir::READ => Poll::Ready(Ok(SlaveCommand {
kind: SlaveCommandKind::Read,
address: self.determine_matched_address()?,
})),
i2c::vals::Dir::WRITE => {
trace!("DIR: write");
Poll::Ready(Ok(SlaveCommand {
kind: SlaveCommandKind::Write,
address: self.determine_matched_address()?,
}))
}
i2c::vals::Dir::READ => {
trace!("DIR: read");
Poll::Ready(Ok(SlaveCommand {
kind: SlaveCommandKind::Read,
address: self.determine_matched_address()?,
}))
}
}
}
})
@ -971,7 +1131,9 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> {
}
/// Respond to a write command.
pub fn blocking_respond_to_write(&self, read: &mut [u8]) -> Result<(), Error> {
///
/// Returns total number of bytes received.
pub fn blocking_respond_to_write(&self, read: &mut [u8]) -> Result<usize, Error> {
let timeout = self.timeout();
self.slave_read_internal(read, timeout)
}
@ -1025,7 +1187,7 @@ impl<'d> I2c<'d, Async, MultiMaster> {
w.set_rxdmaen(false);
w.set_stopie(false);
w.set_tcie(false);
})
});
});
let total_received = poll_fn(|cx| {

View File

@ -115,6 +115,31 @@ impl<'d, T: Instance> Timer<'d, T> {
.ccmr(0)
.modify(|w| w.set_ccsel(channel.index(), direction.into()));
}
/// Enable the timer interrupt.
pub fn enable_interrupt(&self) {
T::regs().dier().modify(|w| w.set_arrmie(true));
}
/// Disable the timer interrupt.
pub fn disable_interrupt(&self) {
T::regs().dier().modify(|w| w.set_arrmie(false));
}
/// Check if the timer interrupt is enabled.
pub fn is_interrupt_enabled(&self) -> bool {
T::regs().dier().read().arrmie()
}
/// Check if the timer interrupt is pending.
pub fn is_interrupt_pending(&self) -> bool {
T::regs().isr().read().arrm()
}
/// Clear the timer interrupt.
pub fn clear_interrupt(&self) {
T::regs().icr().write(|w| w.set_arrmcf(true));
}
}
#[cfg(not(any(lptim_v2a, lptim_v2b)))]
@ -128,4 +153,29 @@ impl<'d, T: Instance> Timer<'d, T> {
pub fn get_compare_value(&self) -> u16 {
T::regs().cmp().read().cmp()
}
/// Enable the timer interrupt.
pub fn enable_interrupt(&self) {
T::regs().ier().modify(|w| w.set_arrmie(true));
}
/// Disable the timer interrupt.
pub fn disable_interrupt(&self) {
T::regs().ier().modify(|w| w.set_arrmie(false));
}
/// Check if the timer interrupt is enabled.
pub fn is_interrupt_enabled(&self) -> bool {
T::regs().ier().read().arrmie()
}
/// Check if the timer interrupt is pending.
pub fn is_interrupt_pending(&self) -> bool {
T::regs().isr().read().arrm()
}
/// Clear the timer interrupt.
pub fn clear_interrupt(&self) {
T::regs().icr().write(|w| w.set_arrmcf(true));
}
}

View File

@ -5,7 +5,7 @@ pub use crate::pac::rcc::vals::{
Hpre as AHBPrescaler, Msirange, Msirange as MSIRange, Plldiv as PllDiv, Pllm as PllPreDiv, Plln as PllMul,
Pllsrc as PllSource, Ppre as APBPrescaler, Sw as Sysclk,
};
use crate::pac::rcc::vals::{Hseext, Msirgsel, Pllmboost, Pllrge};
use crate::pac::rcc::vals::{Hseext, Msipllfast, Msipllsel, Msirgsel, Pllmboost, Pllrge};
#[cfg(all(peri_usb_otg_hs))]
pub use crate::pac::{syscfg::vals::Usbrefcksel, SYSCFG};
use crate::pac::{FLASH, PWR, RCC};
@ -64,6 +64,46 @@ pub struct Pll {
pub divr: Option<PllDiv>,
}
#[derive(Clone, Copy, PartialEq)]
pub enum MsiAutoCalibration {
/// MSI auto-calibration is disabled
Disabled,
/// MSIS is given priority for auto-calibration
MSIS,
/// MSIK is given priority for auto-calibration
MSIK,
/// MSIS with fast mode (always on)
MsisFast,
/// MSIK with fast mode (always on)
MsikFast,
}
impl MsiAutoCalibration {
const fn default() -> Self {
MsiAutoCalibration::Disabled
}
fn base_mode(&self) -> Self {
match self {
MsiAutoCalibration::Disabled => MsiAutoCalibration::Disabled,
MsiAutoCalibration::MSIS => MsiAutoCalibration::MSIS,
MsiAutoCalibration::MSIK => MsiAutoCalibration::MSIK,
MsiAutoCalibration::MsisFast => MsiAutoCalibration::MSIS,
MsiAutoCalibration::MsikFast => MsiAutoCalibration::MSIK,
}
}
fn is_fast(&self) -> bool {
matches!(self, MsiAutoCalibration::MsisFast | MsiAutoCalibration::MsikFast)
}
}
impl Default for MsiAutoCalibration {
fn default() -> Self {
Self::default()
}
}
#[derive(Clone, Copy)]
pub struct Config {
// base clock sources
@ -95,6 +135,7 @@ pub struct Config {
/// Per-peripheral kernel clock selection muxes
pub mux: super::mux::ClockMux,
pub auto_calibration: MsiAutoCalibration,
}
impl Config {
@ -116,6 +157,7 @@ impl Config {
voltage_range: VoltageScale::RANGE1,
ls: crate::rcc::LsConfig::new(),
mux: super::mux::ClockMux::default(),
auto_calibration: MsiAutoCalibration::default(),
}
}
}
@ -131,7 +173,42 @@ pub(crate) unsafe fn init(config: Config) {
PWR.vosr().modify(|w| w.set_vos(config.voltage_range));
while !PWR.vosr().read().vosrdy() {}
let msis = config.msis.map(|range| {
let lse_calibration_freq = if config.auto_calibration != MsiAutoCalibration::Disabled {
// LSE must be configured and peripherals clocked for MSI auto-calibration
let lse_config = config
.ls
.lse
.clone()
.expect("LSE must be configured for MSI auto-calibration");
assert!(lse_config.peripherals_clocked);
// Expect less than +/- 5% deviation for LSE frequency
if (31_100..=34_400).contains(&lse_config.frequency.0) {
// Check that the calibration is applied to an active clock
match (
config.auto_calibration.base_mode(),
config.msis.is_some(),
config.msik.is_some(),
) {
(MsiAutoCalibration::MSIS, true, _) => {
// MSIS is active and using LSE for auto-calibration
Some(lse_config.frequency)
}
(MsiAutoCalibration::MSIK, _, true) => {
// MSIK is active and using LSE for auto-calibration
Some(lse_config.frequency)
}
// improper configuration
_ => panic!("MSIx auto-calibration is enabled for a source that has not been configured."),
}
} else {
panic!("LSE frequency more than 5% off from 32.768 kHz, cannot use for MSI auto-calibration");
}
} else {
None
};
let mut msis = config.msis.map(|range| {
// Check MSI output per RM0456 § 11.4.10
match config.voltage_range {
VoltageScale::RANGE4 => {
@ -156,11 +233,21 @@ pub(crate) unsafe fn init(config: Config) {
w.set_msipllen(false);
w.set_msison(true);
});
let msis = if let (Some(freq), MsiAutoCalibration::MSIS) =
(lse_calibration_freq, config.auto_calibration.base_mode())
{
// Enable the MSIS auto-calibration feature
RCC.cr().modify(|w| w.set_msipllsel(Msipllsel::MSIS));
RCC.cr().modify(|w| w.set_msipllen(true));
calculate_calibrated_msi_frequency(range, freq)
} else {
msirange_to_hertz(range)
};
while !RCC.cr().read().msisrdy() {}
msirange_to_hertz(range)
msis
});
let msik = config.msik.map(|range| {
let mut msik = config.msik.map(|range| {
// Check MSI output per RM0456 § 11.4.10
match config.voltage_range {
VoltageScale::RANGE4 => {
@ -184,10 +271,44 @@ pub(crate) unsafe fn init(config: Config) {
RCC.cr().modify(|w| {
w.set_msikon(true);
});
let msik = if let (Some(freq), MsiAutoCalibration::MSIK) =
(lse_calibration_freq, config.auto_calibration.base_mode())
{
// Enable the MSIK auto-calibration feature
RCC.cr().modify(|w| w.set_msipllsel(Msipllsel::MSIK));
RCC.cr().modify(|w| w.set_msipllen(true));
calculate_calibrated_msi_frequency(range, freq)
} else {
msirange_to_hertz(range)
};
while !RCC.cr().read().msikrdy() {}
msirange_to_hertz(range)
msik
});
if let Some(lse_freq) = lse_calibration_freq {
// If both MSIS and MSIK are enabled, we need to check if they are using the same internal source.
if let (Some(msis_range), Some(msik_range)) = (config.msis, config.msik) {
if (msis_range as u8 >> 2) == (msik_range as u8 >> 2) {
// Clock source is shared, both will be auto calibrated, recalculate other frequency
match config.auto_calibration.base_mode() {
MsiAutoCalibration::MSIS => {
msik = Some(calculate_calibrated_msi_frequency(msik_range, lse_freq));
}
MsiAutoCalibration::MSIK => {
msis = Some(calculate_calibrated_msi_frequency(msis_range, lse_freq));
}
_ => {}
}
}
}
// Check if Fast mode should be used
if config.auto_calibration.is_fast() {
RCC.cr().modify(|w| {
w.set_msipllfast(Msipllfast::FAST);
});
}
}
let hsi = config.hsi.then(|| {
RCC.cr().modify(|w| w.set_hsion(true));
while !RCC.cr().read().hsirdy() {}
@ -514,3 +635,37 @@ fn init_pll(instance: PllInstance, config: Option<Pll>, input: &PllInput, voltag
PllOutput { p, q, r }
}
/// Fraction structure for MSI auto-calibration
/// Represents the multiplier as numerator/denominator that LSE frequency is multiplied by
#[derive(Debug, Clone, Copy)]
struct MsiFraction {
numerator: u32,
denominator: u32,
}
impl MsiFraction {
const fn new(numerator: u32, denominator: u32) -> Self {
Self { numerator, denominator }
}
/// Calculate the calibrated frequency given an LSE frequency
fn calculate_frequency(&self, lse_freq: Hertz) -> Hertz {
Hertz(lse_freq.0 * self.numerator / self.denominator)
}
}
fn get_msi_calibration_fraction(range: Msirange) -> MsiFraction {
// Exploiting the MSIx internals to make calculations compact
let denominator = (range as u32 & 0x03) + 1;
// Base multipliers are deduced from Table 82: MSI oscillator characteristics in data sheet
let numerator = [1465, 122, 94, 12][range as usize >> 2];
MsiFraction::new(numerator, denominator)
}
/// Calculate the calibrated MSI frequency for a given range and LSE frequency
fn calculate_calibrated_msi_frequency(range: Msirange, lse_freq: Hertz) -> Hertz {
let fraction = get_msi_calibration_fraction(range);
fraction.calculate_frequency(lse_freq)
}

View File

@ -77,6 +77,7 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> {
this.inner.set_output_compare_mode(channel, OutputCompareMode::PwmMode1);
this.inner.set_output_compare_preload(channel, true);
});
this.inner.set_autoreload_preload(true);
this
}
@ -110,7 +111,11 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> {
///
/// This value depends on the configured frequency and the timer's clock rate from RCC.
pub fn get_max_duty(&self) -> u16 {
self.inner.get_max_compare_value() as u16 + 1
if self.inner.get_counting_mode().is_center_aligned() {
self.inner.get_max_compare_value() as u16
} else {
self.inner.get_max_compare_value() as u16 + 1
}
}
/// Set the duty for a given channel.
@ -160,7 +165,11 @@ impl<'d, T: AdvancedInstance4Channel> embedded_hal_02::Pwm for ComplementaryPwm<
}
fn get_max_duty(&self) -> Self::Duty {
self.inner.get_max_compare_value() as u16 + 1
if self.inner.get_counting_mode().is_center_aligned() {
self.inner.get_max_compare_value() as u16
} else {
self.inner.get_max_compare_value() as u16 + 1
}
}
fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) {

View File

@ -27,6 +27,7 @@ use embassy_stm32_wpan::hci::vendor::event::{self, AttributeHandle, VendorEvent}
use embassy_stm32_wpan::hci::{BdAddr, Event};
use embassy_stm32_wpan::lhci::LhciC1DeviceInformationCcrp;
use embassy_stm32_wpan::sub::ble::Ble;
use embassy_stm32_wpan::sub::mm;
use embassy_stm32_wpan::TlMbox;
use {defmt_rtt as _, panic_probe as _};
@ -38,7 +39,7 @@ bind_interrupts!(struct Irqs{
const BLE_GAP_DEVICE_NAME_LENGTH: u8 = 7;
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
async fn main(spawner: Spawner) {
/*
How to make this work:
@ -70,6 +71,7 @@ async fn main(_spawner: Spawner) {
let config = Config::default();
let mut mbox = TlMbox::init(p.IPCC, Irqs, config);
spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap();
let sys_event = mbox.sys_subsystem.read().await;
info!("sys event: {}", sys_event.payload());
@ -221,6 +223,11 @@ async fn main(_spawner: Spawner) {
}
}
#[embassy_executor::task]
async fn run_mm_queue(memory_manager: mm::MemoryManager) {
memory_manager.run_queue().await;
}
fn get_bd_addr() -> BdAddr {
let mut bytes = [0u8; 6];