mirror of
https://github.com/embassy-rs/embassy.git
synced 2024-11-22 06:42:32 +00:00
Merge branch 'main' into nrf9151
This commit is contained in:
commit
38d8abef26
1
ci.sh
1
ci.sh
@ -201,6 +201,7 @@ cargo batch \
|
||||
--- build --release --manifest-path examples/stm32g4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32g4 \
|
||||
--- build --release --manifest-path examples/stm32h5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32h5 \
|
||||
--- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7 \
|
||||
--- build --release --manifest-path examples/stm32h735/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h735 \
|
||||
--- build --release --manifest-path examples/stm32h7rs/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7rs \
|
||||
--- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32l0 \
|
||||
--- build --release --manifest-path examples/stm32l1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32l1 \
|
||||
|
@ -353,7 +353,12 @@ There are two main ways to handle concurrency in Embassy:
|
||||
In general, either of these approaches will work. The main differences of these approaches are:
|
||||
|
||||
When using **separate tasks**, each task needs its own RAM allocation, so there's a little overhead for each task, so one task that does three things will likely be a little bit smaller than three tasks that do one thing (not a lot, probably a couple dozen bytes). In contrast, with **multiple futures in one task**, you don't need multiple task allocations, and it will generally be easier to share data, or use borrowed resources, inside of a single task.
|
||||
An example showcasing some methods for sharing things between tasks link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/sharing.rs[can be found here].
|
||||
|
||||
But when it comes to "waking" tasks, for example when a data transfer is complete or a button is pressed, it's faster to wake a dedicated task, because that task does not need to check which future is actually ready. `join` and `select` must check ALL of the futures they are managing to see which one (or which ones) are ready to do more work. This is because all Rust executors (like Embassy or Tokio) only have the ability to wake tasks, not specific futures. This means you will use slightly less CPU time juggling futures when using dedicated tasks.
|
||||
|
||||
Practically, there's not a LOT of difference either way - so go with what makes it easier for you and your code first, but there will be some details that are slightly different in each case.
|
||||
|
||||
== splitting peripherals resources between tasks
|
||||
|
||||
There are two ways to split resources between tasks, either manually assigned or by a convenient macro. See link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/assign_resources.rs[this example]
|
@ -126,3 +126,5 @@ async fn toggle_led(control: Sender<'static, ThreadModeRawMutex, LedState, 64>,
|
||||
|
||||
This example replaces the Mutex with a Channel, and uses another task (the main loop) to drive the LED. The advantage of this approach is that only a single task references the peripheral, separating concerns. However, using a Mutex has a lower overhead and might be necessary if you need to ensure
|
||||
that the operation is completed before continuing to do other work in your task.
|
||||
|
||||
An example showcasing more methods for sharing link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/sharing.rs[can be found here].
|
@ -235,12 +235,15 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
|
||||
/// | DFU | 3 | 4 | 5 | 6 | 3 |
|
||||
///
|
||||
pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> {
|
||||
const {
|
||||
assert!(Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32 == 0);
|
||||
assert!(Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32 == 0);
|
||||
assert!(Self::PAGE_SIZE % DFU::WRITE_SIZE as u32 == 0);
|
||||
assert!(Self::PAGE_SIZE % DFU::ERASE_SIZE as u32 == 0);
|
||||
}
|
||||
|
||||
// Ensure we have enough progress pages to store copy progress
|
||||
assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32);
|
||||
assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32);
|
||||
assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32);
|
||||
assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32);
|
||||
assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32);
|
||||
assert!(aligned_buf.len() >= STATE::WRITE_SIZE);
|
||||
assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE);
|
||||
assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE);
|
||||
|
@ -8,10 +8,17 @@ pub use w5100s::W5100S;
|
||||
pub(crate) trait SealedChip {
|
||||
type Address;
|
||||
|
||||
/// The version of the chip as reported by the VERSIONR register.
|
||||
/// This is used to verify that the chip is supported by the driver,
|
||||
/// and that SPI communication is working.
|
||||
const CHIP_VERSION: u8;
|
||||
|
||||
const COMMON_MODE: Self::Address;
|
||||
const COMMON_MAC: Self::Address;
|
||||
const COMMON_SOCKET_INTR: Self::Address;
|
||||
const COMMON_PHY_CFG: Self::Address;
|
||||
const COMMON_VERSION: Self::Address;
|
||||
|
||||
const SOCKET_MODE: Self::Address;
|
||||
const SOCKET_COMMAND: Self::Address;
|
||||
const SOCKET_RXBUF_SIZE: Self::Address;
|
||||
|
@ -11,10 +11,13 @@ impl super::Chip for W5100S {}
|
||||
impl super::SealedChip for W5100S {
|
||||
type Address = u16;
|
||||
|
||||
const CHIP_VERSION: u8 = 0x51;
|
||||
|
||||
const COMMON_MODE: Self::Address = 0x00;
|
||||
const COMMON_MAC: Self::Address = 0x09;
|
||||
const COMMON_SOCKET_INTR: Self::Address = 0x16;
|
||||
const COMMON_PHY_CFG: Self::Address = 0x3c;
|
||||
const COMMON_VERSION: Self::Address = 0x80;
|
||||
|
||||
const SOCKET_MODE: Self::Address = SOCKET_BASE + 0x00;
|
||||
const SOCKET_COMMAND: Self::Address = SOCKET_BASE + 0x01;
|
||||
|
@ -15,10 +15,13 @@ impl super::Chip for W5500 {}
|
||||
impl super::SealedChip for W5500 {
|
||||
type Address = (RegisterBlock, u16);
|
||||
|
||||
const CHIP_VERSION: u8 = 0x04;
|
||||
|
||||
const COMMON_MODE: Self::Address = (RegisterBlock::Common, 0x00);
|
||||
const COMMON_MAC: Self::Address = (RegisterBlock::Common, 0x09);
|
||||
const COMMON_SOCKET_INTR: Self::Address = (RegisterBlock::Common, 0x18);
|
||||
const COMMON_PHY_CFG: Self::Address = (RegisterBlock::Common, 0x2E);
|
||||
const COMMON_VERSION: Self::Address = (RegisterBlock::Common, 0x39);
|
||||
|
||||
const SOCKET_MODE: Self::Address = (RegisterBlock::Socket0, 0x00);
|
||||
const SOCKET_COMMAND: Self::Address = (RegisterBlock::Socket0, 0x01);
|
||||
|
@ -24,9 +24,57 @@ pub(crate) struct WiznetDevice<C, SPI> {
|
||||
_phantom: PhantomData<C>,
|
||||
}
|
||||
|
||||
/// Error type when initializing a new Wiznet device
|
||||
pub enum InitError<SE> {
|
||||
/// Error occurred when sending or receiving SPI data
|
||||
SpiError(SE),
|
||||
/// The chip returned a version that isn't expected or supported
|
||||
InvalidChipVersion {
|
||||
/// The version that is supported
|
||||
expected: u8,
|
||||
/// The version that was returned by the chip
|
||||
actual: u8,
|
||||
},
|
||||
}
|
||||
|
||||
impl<SE> From<SE> for InitError<SE> {
|
||||
fn from(e: SE) -> Self {
|
||||
InitError::SpiError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<SE> core::fmt::Debug for InitError<SE>
|
||||
where
|
||||
SE: core::fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
InitError::SpiError(e) => write!(f, "SpiError({:?})", e),
|
||||
InitError::InvalidChipVersion { expected, actual } => {
|
||||
write!(f, "InvalidChipVersion {{ expected: {}, actual: {} }}", expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl<SE> defmt::Format for InitError<SE>
|
||||
where
|
||||
SE: defmt::Format,
|
||||
{
|
||||
fn format(&self, f: defmt::Formatter) {
|
||||
match self {
|
||||
InitError::SpiError(e) => defmt::write!(f, "SpiError({})", e),
|
||||
InitError::InvalidChipVersion { expected, actual } => {
|
||||
defmt::write!(f, "InvalidChipVersion {{ expected: {}, actual: {} }}", expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Chip, SPI: SpiDevice> WiznetDevice<C, SPI> {
|
||||
/// Create and initialize the driver
|
||||
pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result<Self, SPI::Error> {
|
||||
pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result<Self, InitError<SPI::Error>> {
|
||||
let mut this = Self {
|
||||
spi,
|
||||
_phantom: PhantomData,
|
||||
@ -35,6 +83,18 @@ impl<C: Chip, SPI: SpiDevice> WiznetDevice<C, SPI> {
|
||||
// Reset device
|
||||
this.bus_write(C::COMMON_MODE, &[0x80]).await?;
|
||||
|
||||
// Check the version of the chip
|
||||
let mut version = [0];
|
||||
this.bus_read(C::COMMON_VERSION, &mut version).await?;
|
||||
if version[0] != C::CHIP_VERSION {
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::error!("invalid chip version: {} (expected {})", version[0], C::CHIP_VERSION);
|
||||
return Err(InitError::InvalidChipVersion {
|
||||
actual: version[0],
|
||||
expected: C::CHIP_VERSION,
|
||||
});
|
||||
}
|
||||
|
||||
// Enable interrupt pin
|
||||
this.bus_write(C::COMMON_SOCKET_INTR, &[0x01]).await?;
|
||||
// Enable receive interrupt
|
||||
|
@ -15,6 +15,7 @@ use embedded_hal_async::digital::Wait;
|
||||
use embedded_hal_async::spi::SpiDevice;
|
||||
|
||||
use crate::chip::Chip;
|
||||
pub use crate::device::InitError;
|
||||
use crate::device::WiznetDevice;
|
||||
|
||||
// If you change this update the docs of State
|
||||
@ -105,7 +106,7 @@ pub async fn new<'a, const N_RX: usize, const N_TX: usize, C: Chip, SPI: SpiDevi
|
||||
spi_dev: SPI,
|
||||
int: INT,
|
||||
mut reset: RST,
|
||||
) -> (Device<'a>, Runner<'a, C, SPI, INT, RST>) {
|
||||
) -> Result<(Device<'a>, Runner<'a, C, SPI, INT, RST>), InitError<SPI::Error>> {
|
||||
// Reset the chip.
|
||||
reset.set_low().ok();
|
||||
// Ensure the reset is registered.
|
||||
@ -116,10 +117,11 @@ pub async fn new<'a, const N_RX: usize, const N_TX: usize, C: Chip, SPI: SpiDevi
|
||||
// Slowest is w5100s which is 100ms, so let's just wait that.
|
||||
Timer::after_millis(100).await;
|
||||
|
||||
let mac = WiznetDevice::new(spi_dev, mac_addr).await.unwrap();
|
||||
let mac = WiznetDevice::new(spi_dev, mac_addr).await?;
|
||||
|
||||
let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ethernet(mac_addr));
|
||||
(
|
||||
|
||||
Ok((
|
||||
device,
|
||||
Runner {
|
||||
ch: runner,
|
||||
@ -127,5 +129,5 @@ pub async fn new<'a, const N_RX: usize, const N_TX: usize, C: Chip, SPI: SpiDevi
|
||||
int,
|
||||
_reset: reset,
|
||||
},
|
||||
)
|
||||
))
|
||||
}
|
||||
|
@ -603,7 +603,7 @@ mod embedded_io_impls {
|
||||
|
||||
impl<'d> embedded_io_async::WriteReady for TcpSocket<'d> {
|
||||
fn write_ready(&mut self) -> Result<bool, Self::Error> {
|
||||
Ok(self.io.with(|s, _| s.may_send()))
|
||||
Ok(self.io.with(|s, _| s.can_send()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -619,7 +619,7 @@ mod embedded_io_impls {
|
||||
|
||||
impl<'d> embedded_io_async::ReadReady for TcpReader<'d> {
|
||||
fn read_ready(&mut self) -> Result<bool, Self::Error> {
|
||||
Ok(self.io.with(|s, _| s.can_recv()))
|
||||
Ok(self.io.with(|s, _| s.can_recv() || !s.may_recv()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -639,7 +639,7 @@ mod embedded_io_impls {
|
||||
|
||||
impl<'d> embedded_io_async::WriteReady for TcpWriter<'d> {
|
||||
fn write_ready(&mut self) -> Result<bool, Self::Error> {
|
||||
Ok(self.io.with(|s, _| s.may_send()))
|
||||
Ok(self.io.with(|s, _| s.can_send()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- spi: Add support for configuring bit order for bus
|
||||
- pwm: Expose `pwm::PWM_CLK_HZ` and add `is_enabled` method
|
||||
- gpio: Drop GPIO Pin generics (API break)
|
||||
- pwm: Allow specifying OutputDrive for PWM channels
|
||||
|
||||
## 0.1.0 - 2024-01-12
|
||||
|
||||
|
@ -6,7 +6,7 @@ use core::sync::atomic::{compiler_fence, Ordering};
|
||||
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
|
||||
use crate::gpio::{AnyPin, Pin as GpioPin, PselBits, SealedPin as _};
|
||||
use crate::gpio::{convert_drive, AnyPin, OutputDrive, Pin as GpioPin, PselBits, SealedPin as _};
|
||||
use crate::ppi::{Event, Task};
|
||||
use crate::util::slice_in_ram_or;
|
||||
use crate::{interrupt, pac, Peripheral};
|
||||
@ -128,19 +128,23 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
|
||||
|
||||
if let Some(pin) = &ch0 {
|
||||
pin.set_low();
|
||||
pin.conf().write(|w| w.dir().output());
|
||||
pin.conf()
|
||||
.write(|w| w.dir().output().drive().variant(convert_drive(config.ch0_drive)));
|
||||
}
|
||||
if let Some(pin) = &ch1 {
|
||||
pin.set_low();
|
||||
pin.conf().write(|w| w.dir().output());
|
||||
pin.conf()
|
||||
.write(|w| w.dir().output().drive().variant(convert_drive(config.ch1_drive)));
|
||||
}
|
||||
if let Some(pin) = &ch2 {
|
||||
pin.set_low();
|
||||
pin.conf().write(|w| w.dir().output());
|
||||
pin.conf()
|
||||
.write(|w| w.dir().output().drive().variant(convert_drive(config.ch2_drive)));
|
||||
}
|
||||
if let Some(pin) = &ch3 {
|
||||
pin.set_low();
|
||||
pin.conf().write(|w| w.dir().output());
|
||||
pin.conf()
|
||||
.write(|w| w.dir().output().drive().variant(convert_drive(config.ch3_drive)));
|
||||
}
|
||||
|
||||
r.psel.out[0].write(|w| unsafe { w.bits(ch0.psel_bits()) });
|
||||
@ -319,6 +323,14 @@ pub struct Config {
|
||||
pub prescaler: Prescaler,
|
||||
/// How a sequence is read from RAM and is spread to the compare register
|
||||
pub sequence_load: SequenceLoad,
|
||||
/// Drive strength for the channel 0 line.
|
||||
pub ch0_drive: OutputDrive,
|
||||
/// Drive strength for the channel 1 line.
|
||||
pub ch1_drive: OutputDrive,
|
||||
/// Drive strength for the channel 2 line.
|
||||
pub ch2_drive: OutputDrive,
|
||||
/// Drive strength for the channel 3 line.
|
||||
pub ch3_drive: OutputDrive,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@ -328,6 +340,10 @@ impl Default for Config {
|
||||
max_duty: 1000,
|
||||
prescaler: Prescaler::Div16,
|
||||
sequence_load: SequenceLoad::Common,
|
||||
ch0_drive: OutputDrive::Standard,
|
||||
ch1_drive: OutputDrive::Standard,
|
||||
ch2_drive: OutputDrive::Standard,
|
||||
ch3_drive: OutputDrive::Standard,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -815,6 +831,38 @@ impl<'d, T: Instance> SimplePwm<'d, T> {
|
||||
let max_duty = self.max_duty() as u32;
|
||||
clk / max_duty
|
||||
}
|
||||
|
||||
/// Sets the PWM-Channel0 output drive strength
|
||||
#[inline(always)]
|
||||
pub fn set_ch0_drive(&self, drive: OutputDrive) {
|
||||
if let Some(pin) = &self.ch0 {
|
||||
pin.conf().modify(|_, w| w.drive().variant(convert_drive(drive)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the PWM-Channel1 output drive strength
|
||||
#[inline(always)]
|
||||
pub fn set_ch1_drive(&self, drive: OutputDrive) {
|
||||
if let Some(pin) = &self.ch1 {
|
||||
pin.conf().modify(|_, w| w.drive().variant(convert_drive(drive)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the PWM-Channel2 output drive strength
|
||||
#[inline(always)]
|
||||
pub fn set_ch2_drive(&self, drive: OutputDrive) {
|
||||
if let Some(pin) = &self.ch2 {
|
||||
pin.conf().modify(|_, w| w.drive().variant(convert_drive(drive)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the PWM-Channel3 output drive strength
|
||||
#[inline(always)]
|
||||
pub fn set_ch3_drive(&self, drive: OutputDrive) {
|
||||
if let Some(pin) = &self.ch3 {
|
||||
pin.conf().modify(|_, w| w.drive().variant(convert_drive(drive)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Instance> Drop for SimplePwm<'a, T> {
|
||||
|
@ -335,8 +335,6 @@ impl<'d, T: Instance> Radio<'d, T> {
|
||||
}
|
||||
|
||||
async fn trigger_and_wait_end(&mut self, trigger: impl FnOnce()) {
|
||||
//self.trace_state();
|
||||
|
||||
let r = T::regs();
|
||||
let s = T::state();
|
||||
|
||||
@ -347,12 +345,10 @@ impl<'d, T: Instance> Radio<'d, T> {
|
||||
trace!("radio drop: stopping");
|
||||
|
||||
r.intenclr.write(|w| w.end().clear());
|
||||
r.events_end.reset();
|
||||
|
||||
r.tasks_stop.write(|w| unsafe { w.bits(1) });
|
||||
|
||||
// The docs don't explicitly mention any event to acknowledge the stop task
|
||||
while r.events_end.read().bits() == 0 {}
|
||||
r.events_end.reset();
|
||||
|
||||
trace!("radio drop: stopped");
|
||||
});
|
||||
@ -368,7 +364,6 @@ impl<'d, T: Instance> Radio<'d, T> {
|
||||
|
||||
// Trigger the transmission
|
||||
trigger();
|
||||
// self.trace_state();
|
||||
|
||||
// On poll check if interrupt happen
|
||||
poll_fn(|cx| {
|
||||
@ -382,7 +377,7 @@ impl<'d, T: Instance> Radio<'d, T> {
|
||||
.await;
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
r.events_disabled.reset(); // ACK
|
||||
r.events_end.reset(); // ACK
|
||||
|
||||
// Everthing ends fine, so it disable the drop
|
||||
drop.defuse();
|
||||
|
@ -315,6 +315,12 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> {
|
||||
w.set_rtim(true);
|
||||
});
|
||||
}
|
||||
|
||||
/// we are ready to read if there is data in the buffer
|
||||
fn read_ready() -> Result<bool, Error> {
|
||||
let state = T::buffered_state();
|
||||
Ok(!state.rx_buf.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> BufferedUartTx<'d, T> {
|
||||
@ -621,6 +627,18 @@ impl<'d, T: Instance + 'd> embedded_io_async::Read for BufferedUartRx<'d, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance + 'd> embedded_io_async::ReadReady for BufferedUart<'d, T> {
|
||||
fn read_ready(&mut self) -> Result<bool, Self::Error> {
|
||||
BufferedUartRx::<'d, T>::read_ready()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance + 'd> embedded_io_async::ReadReady for BufferedUartRx<'d, T> {
|
||||
fn read_ready(&mut self) -> Result<bool, Self::Error> {
|
||||
Self::read_ready()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance + 'd> embedded_io_async::BufRead for BufferedUart<'d, T> {
|
||||
async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> {
|
||||
BufferedUartRx::<'d, T>::fill_buf().await
|
||||
|
@ -72,7 +72,7 @@ rand_core = "0.6.3"
|
||||
sdio-host = "0.5.0"
|
||||
critical-section = "1.1"
|
||||
#stm32-metapac = { version = "15" }
|
||||
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-a8ab0a3421ed0ca4b282f54028a0a2decacd8631" }
|
||||
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-e0cfd165fd8fffaa0df66a35eeca83b228496645" }
|
||||
|
||||
vcell = "0.1.3"
|
||||
nb = "1.0.0"
|
||||
@ -97,7 +97,7 @@ proc-macro2 = "1.0.36"
|
||||
quote = "1.0.15"
|
||||
|
||||
#stm32-metapac = { version = "15", default-features = false, features = ["metadata"]}
|
||||
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-a8ab0a3421ed0ca4b282f54028a0a2decacd8631", default-features = false, features = ["metadata"] }
|
||||
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-e0cfd165fd8fffaa0df66a35eeca83b228496645", default-features = false, features = ["metadata"] }
|
||||
|
||||
[features]
|
||||
default = ["rt"]
|
||||
|
@ -475,13 +475,21 @@ fn main() {
|
||||
}
|
||||
|
||||
impl<'a> ClockGen<'a> {
|
||||
fn gen_clock(&mut self, name: &str) -> TokenStream {
|
||||
fn gen_clock(&mut self, peripheral: &str, name: &str) -> TokenStream {
|
||||
let clock_name = format_ident!("{}", name.to_ascii_lowercase());
|
||||
self.clock_names.insert(name.to_ascii_lowercase());
|
||||
quote!( unsafe { crate::rcc::get_freqs().#clock_name.unwrap() } )
|
||||
quote!(unsafe {
|
||||
unwrap!(
|
||||
crate::rcc::get_freqs().#clock_name,
|
||||
"peripheral '{}' is configured to use the '{}' clock, which is not running. \
|
||||
Either enable it in 'config.rcc' or change 'config.rcc.mux' to use another clock",
|
||||
#peripheral,
|
||||
#name
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn gen_mux(&mut self, mux: &PeripheralRccRegister) -> TokenStream {
|
||||
fn gen_mux(&mut self, peripheral: &str, mux: &PeripheralRccRegister) -> TokenStream {
|
||||
let ir = &self.rcc_registers.ir;
|
||||
let fieldset_name = mux.register.to_ascii_lowercase();
|
||||
let fieldset = ir
|
||||
@ -506,9 +514,9 @@ fn main() {
|
||||
for v in enumm.variants.iter().filter(|v| v.name != "DISABLE") {
|
||||
let variant_name = format_ident!("{}", v.name);
|
||||
let expr = if let Some(mux) = self.chained_muxes.get(&v.name) {
|
||||
self.gen_mux(mux)
|
||||
self.gen_mux(peripheral, mux)
|
||||
} else {
|
||||
self.gen_clock(v.name)
|
||||
self.gen_clock(peripheral, v.name)
|
||||
};
|
||||
match_arms.extend(quote! {
|
||||
crate::pac::rcc::vals::#enum_name::#variant_name => #expr,
|
||||
@ -586,8 +594,8 @@ fn main() {
|
||||
};
|
||||
|
||||
let clock_frequency = match &rcc.kernel_clock {
|
||||
PeripheralRccKernelClock::Mux(mux) => clock_gen.gen_mux(mux),
|
||||
PeripheralRccKernelClock::Clock(clock) => clock_gen.gen_clock(clock),
|
||||
PeripheralRccKernelClock::Mux(mux) => clock_gen.gen_mux(p.name, mux),
|
||||
PeripheralRccKernelClock::Clock(clock) => clock_gen.gen_clock(p.name, clock),
|
||||
};
|
||||
|
||||
// A refcount leak can result if the same field is shared by peripherals with different stop modes
|
||||
@ -764,7 +772,7 @@ fn main() {
|
||||
|
||||
#[rustfmt::skip]
|
||||
let signals: HashMap<_, _> = [
|
||||
// (kind, signal) => trait
|
||||
// (kind, signal) => trait
|
||||
(("ucpd", "CC1"), quote!(crate::ucpd::Cc1Pin)),
|
||||
(("ucpd", "CC2"), quote!(crate::ucpd::Cc2Pin)),
|
||||
(("usart", "TX"), quote!(crate::usart::TxPin)),
|
||||
@ -1178,6 +1186,11 @@ fn main() {
|
||||
|
||||
let signals: HashMap<_, _> = [
|
||||
// (kind, signal) => trait
|
||||
(("adc", "ADC"), quote!(crate::adc::RxDma)),
|
||||
(("adc", "ADC1"), quote!(crate::adc::RxDma)),
|
||||
(("adc", "ADC2"), quote!(crate::adc::RxDma)),
|
||||
(("adc", "ADC3"), quote!(crate::adc::RxDma)),
|
||||
(("adc", "ADC4"), quote!(crate::adc::RxDma)),
|
||||
(("ucpd", "RX"), quote!(crate::ucpd::RxDma)),
|
||||
(("ucpd", "TX"), quote!(crate::ucpd::TxDma)),
|
||||
(("usart", "RX"), quote!(crate::usart::RxDma)),
|
||||
|
@ -258,7 +258,7 @@ impl<'d, T: Instance> Adc<'d, T> {
|
||||
}
|
||||
|
||||
/// Read an ADC pin.
|
||||
pub fn read(&mut self, channel: &mut impl AdcChannel<T>) -> u16 {
|
||||
pub fn blocking_read(&mut self, channel: &mut impl AdcChannel<T>) -> u16 {
|
||||
channel.setup();
|
||||
|
||||
self.read_channel(channel.channel())
|
||||
|
@ -28,6 +28,8 @@ pub use crate::pac::adc::vals::Res as Resolution;
|
||||
pub use crate::pac::adc::vals::SampleTime;
|
||||
use crate::peripherals;
|
||||
|
||||
dma_trait!(RxDma, Instance);
|
||||
|
||||
/// Analog to Digital driver.
|
||||
pub struct Adc<'d, T: Instance> {
|
||||
#[allow(unused)]
|
||||
|
437
embassy-stm32/src/adc/ringbuffered_v2.rs
Normal file
437
embassy-stm32/src/adc/ringbuffered_v2.rs
Normal file
@ -0,0 +1,437 @@
|
||||
use core::marker::PhantomData;
|
||||
use core::mem;
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
|
||||
use embassy_hal_internal::{into_ref, Peripheral};
|
||||
use stm32_metapac::adc::vals::SampleTime;
|
||||
|
||||
use crate::adc::{Adc, AdcChannel, Instance, RxDma};
|
||||
use crate::dma::ringbuffer::OverrunError;
|
||||
use crate::dma::{Priority, ReadableRingBuffer, TransferOptions};
|
||||
use crate::pac::adc::vals;
|
||||
use crate::rcc;
|
||||
|
||||
fn clear_interrupt_flags(r: crate::pac::adc::Adc) {
|
||||
r.sr().modify(|regs| {
|
||||
regs.set_eoc(false);
|
||||
regs.set_ovr(false);
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(PartialOrd, PartialEq, Debug, Clone, Copy)]
|
||||
pub enum Sequence {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
Five,
|
||||
Six,
|
||||
Seven,
|
||||
Eight,
|
||||
Nine,
|
||||
Ten,
|
||||
Eleven,
|
||||
Twelve,
|
||||
Thirteen,
|
||||
Fourteen,
|
||||
Fifteen,
|
||||
Sixteen,
|
||||
}
|
||||
|
||||
impl From<Sequence> for u8 {
|
||||
fn from(s: Sequence) -> u8 {
|
||||
match s {
|
||||
Sequence::One => 0,
|
||||
Sequence::Two => 1,
|
||||
Sequence::Three => 2,
|
||||
Sequence::Four => 3,
|
||||
Sequence::Five => 4,
|
||||
Sequence::Six => 5,
|
||||
Sequence::Seven => 6,
|
||||
Sequence::Eight => 7,
|
||||
Sequence::Nine => 8,
|
||||
Sequence::Ten => 9,
|
||||
Sequence::Eleven => 10,
|
||||
Sequence::Twelve => 11,
|
||||
Sequence::Thirteen => 12,
|
||||
Sequence::Fourteen => 13,
|
||||
Sequence::Fifteen => 14,
|
||||
Sequence::Sixteen => 15,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Sequence {
|
||||
fn from(val: u8) -> Self {
|
||||
match val {
|
||||
0 => Sequence::One,
|
||||
1 => Sequence::Two,
|
||||
2 => Sequence::Three,
|
||||
3 => Sequence::Four,
|
||||
4 => Sequence::Five,
|
||||
5 => Sequence::Six,
|
||||
6 => Sequence::Seven,
|
||||
7 => Sequence::Eight,
|
||||
8 => Sequence::Nine,
|
||||
9 => Sequence::Ten,
|
||||
10 => Sequence::Eleven,
|
||||
11 => Sequence::Twelve,
|
||||
12 => Sequence::Thirteen,
|
||||
13 => Sequence::Fourteen,
|
||||
14 => Sequence::Fifteen,
|
||||
15 => Sequence::Sixteen,
|
||||
_ => panic!("Invalid sequence number"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RingBufferedAdc<'d, T: Instance> {
|
||||
_phantom: PhantomData<T>,
|
||||
ring_buf: ReadableRingBuffer<'d, u16>,
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> Adc<'d, T> {
|
||||
/// Configures the ADC to use a DMA ring buffer for continuous data acquisition.
|
||||
///
|
||||
/// The `dma_buf` should be large enough to prevent DMA buffer overrun.
|
||||
/// The length of the `dma_buf` should be a multiple of the ADC channel count.
|
||||
/// For example, if 3 channels are measured, its length can be 3 * 40 = 120 measurements.
|
||||
///
|
||||
/// `read` method is used to read out measurements from the DMA ring buffer, and its buffer should be exactly half of the `dma_buf` length.
|
||||
/// It is critical to call `read` frequently to prevent DMA buffer overrun.
|
||||
///
|
||||
/// [`read`]: #method.read
|
||||
pub fn into_ring_buffered(
|
||||
self,
|
||||
dma: impl Peripheral<P = impl RxDma<T>> + 'd,
|
||||
dma_buf: &'d mut [u16],
|
||||
) -> RingBufferedAdc<'d, T> {
|
||||
assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF);
|
||||
into_ref!(dma);
|
||||
|
||||
let opts: crate::dma::TransferOptions = TransferOptions {
|
||||
half_transfer_ir: true,
|
||||
priority: Priority::VeryHigh,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Safety: we forget the struct before this function returns.
|
||||
let rx_src = T::regs().dr().as_ptr() as *mut u16;
|
||||
let request = dma.request();
|
||||
|
||||
let ring_buf = unsafe { ReadableRingBuffer::new(dma, request, rx_src, dma_buf, opts) };
|
||||
|
||||
// Don't disable the clock
|
||||
mem::forget(self);
|
||||
|
||||
RingBufferedAdc {
|
||||
_phantom: PhantomData,
|
||||
ring_buf,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> RingBufferedAdc<'d, T> {
|
||||
fn is_on() -> bool {
|
||||
T::regs().cr2().read().adon()
|
||||
}
|
||||
|
||||
fn stop_adc() {
|
||||
T::regs().cr2().modify(|reg| {
|
||||
reg.set_adon(false);
|
||||
});
|
||||
}
|
||||
|
||||
fn start_adc() {
|
||||
T::regs().cr2().modify(|reg| {
|
||||
reg.set_adon(true);
|
||||
});
|
||||
}
|
||||
|
||||
/// Sets the channel sample time
|
||||
///
|
||||
/// ## SAFETY:
|
||||
/// - ADON == 0 i.e ADC must not be enabled when this is called.
|
||||
unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) {
|
||||
if ch <= 9 {
|
||||
T::regs().smpr2().modify(|reg| reg.set_smp(ch as _, sample_time));
|
||||
} else {
|
||||
T::regs().smpr1().modify(|reg| reg.set_smp((ch - 10) as _, sample_time));
|
||||
}
|
||||
}
|
||||
|
||||
fn set_channels_sample_time(&mut self, ch: &[u8], sample_time: SampleTime) {
|
||||
let ch_iter = ch.iter();
|
||||
for idx in ch_iter {
|
||||
unsafe {
|
||||
Self::set_channel_sample_time(*idx, sample_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_sample_sequence(
|
||||
&mut self,
|
||||
sequence: Sequence,
|
||||
channel: &mut impl AdcChannel<T>,
|
||||
sample_time: SampleTime,
|
||||
) {
|
||||
let was_on = Self::is_on();
|
||||
if !was_on {
|
||||
Self::start_adc();
|
||||
}
|
||||
|
||||
// Check the sequence is long enough
|
||||
T::regs().sqr1().modify(|r| {
|
||||
let prev: Sequence = r.l().into();
|
||||
if prev < sequence {
|
||||
let new_l: Sequence = sequence;
|
||||
trace!("Setting sequence length from {:?} to {:?}", prev as u8, new_l as u8);
|
||||
r.set_l(sequence.into())
|
||||
} else {
|
||||
r.set_l(prev.into())
|
||||
}
|
||||
});
|
||||
|
||||
// Set this GPIO as an analog input.
|
||||
channel.setup();
|
||||
|
||||
// Set the channel in the right sequence field.
|
||||
match sequence {
|
||||
Sequence::One => T::regs().sqr3().modify(|w| w.set_sq(0, channel.channel())),
|
||||
Sequence::Two => T::regs().sqr3().modify(|w| w.set_sq(1, channel.channel())),
|
||||
Sequence::Three => T::regs().sqr3().modify(|w| w.set_sq(2, channel.channel())),
|
||||
Sequence::Four => T::regs().sqr3().modify(|w| w.set_sq(3, channel.channel())),
|
||||
Sequence::Five => T::regs().sqr3().modify(|w| w.set_sq(4, channel.channel())),
|
||||
Sequence::Six => T::regs().sqr3().modify(|w| w.set_sq(5, channel.channel())),
|
||||
Sequence::Seven => T::regs().sqr2().modify(|w| w.set_sq(6, channel.channel())),
|
||||
Sequence::Eight => T::regs().sqr2().modify(|w| w.set_sq(7, channel.channel())),
|
||||
Sequence::Nine => T::regs().sqr2().modify(|w| w.set_sq(8, channel.channel())),
|
||||
Sequence::Ten => T::regs().sqr2().modify(|w| w.set_sq(9, channel.channel())),
|
||||
Sequence::Eleven => T::regs().sqr2().modify(|w| w.set_sq(10, channel.channel())),
|
||||
Sequence::Twelve => T::regs().sqr2().modify(|w| w.set_sq(11, channel.channel())),
|
||||
Sequence::Thirteen => T::regs().sqr1().modify(|w| w.set_sq(12, channel.channel())),
|
||||
Sequence::Fourteen => T::regs().sqr1().modify(|w| w.set_sq(13, channel.channel())),
|
||||
Sequence::Fifteen => T::regs().sqr1().modify(|w| w.set_sq(14, channel.channel())),
|
||||
Sequence::Sixteen => T::regs().sqr1().modify(|w| w.set_sq(15, channel.channel())),
|
||||
};
|
||||
|
||||
if !was_on {
|
||||
Self::stop_adc();
|
||||
}
|
||||
|
||||
self.set_channels_sample_time(&[channel.channel()], sample_time);
|
||||
|
||||
Self::start_adc();
|
||||
}
|
||||
|
||||
/// Turns on ADC if it is not already turned on and starts continuous DMA transfer.
|
||||
pub fn start(&mut self) -> Result<(), OverrunError> {
|
||||
self.ring_buf.clear();
|
||||
|
||||
self.setup_adc();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&mut self, err: OverrunError) -> Result<usize, OverrunError> {
|
||||
self.teardown_adc();
|
||||
Err(err)
|
||||
}
|
||||
|
||||
/// Stops DMA transfer.
|
||||
/// It does not turn off ADC.
|
||||
/// Calling `start` restarts continuous DMA transfer.
|
||||
///
|
||||
/// [`start`]: #method.start
|
||||
pub fn teardown_adc(&mut self) {
|
||||
// Stop the DMA transfer
|
||||
self.ring_buf.request_stop();
|
||||
|
||||
let r = T::regs();
|
||||
|
||||
// Stop ADC
|
||||
r.cr2().modify(|reg| {
|
||||
// Stop ADC
|
||||
reg.set_swstart(false);
|
||||
// Stop DMA
|
||||
reg.set_dma(false);
|
||||
});
|
||||
|
||||
r.cr1().modify(|w| {
|
||||
// Disable interrupt for end of conversion
|
||||
w.set_eocie(false);
|
||||
// Disable interrupt for overrun
|
||||
w.set_ovrie(false);
|
||||
});
|
||||
|
||||
clear_interrupt_flags(r);
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
}
|
||||
|
||||
fn setup_adc(&mut self) {
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
self.ring_buf.start();
|
||||
|
||||
let r = T::regs();
|
||||
|
||||
// Enable ADC
|
||||
let was_on = Self::is_on();
|
||||
if !was_on {
|
||||
r.cr2().modify(|reg| {
|
||||
reg.set_adon(false);
|
||||
reg.set_swstart(false);
|
||||
});
|
||||
}
|
||||
|
||||
// Clear all interrupts
|
||||
r.sr().modify(|regs| {
|
||||
regs.set_eoc(false);
|
||||
regs.set_ovr(false);
|
||||
regs.set_strt(false);
|
||||
});
|
||||
|
||||
r.cr1().modify(|w| {
|
||||
// Enable interrupt for end of conversion
|
||||
w.set_eocie(true);
|
||||
// Enable interrupt for overrun
|
||||
w.set_ovrie(true);
|
||||
// Scanning converisons of multiple channels
|
||||
w.set_scan(true);
|
||||
// Continuous conversion mode
|
||||
w.set_discen(false);
|
||||
});
|
||||
|
||||
r.cr2().modify(|w| {
|
||||
// Enable DMA mode
|
||||
w.set_dma(true);
|
||||
// Enable continuous conversions
|
||||
w.set_cont(true);
|
||||
// DMA requests are issues as long as DMA=1 and data are converted.
|
||||
w.set_dds(vals::Dds::CONTINUOUS);
|
||||
// EOC flag is set at the end of each conversion.
|
||||
w.set_eocs(vals::Eocs::EACHCONVERSION);
|
||||
});
|
||||
|
||||
// Begin ADC conversions
|
||||
T::regs().cr2().modify(|reg| {
|
||||
reg.set_adon(true);
|
||||
reg.set_swstart(true);
|
||||
});
|
||||
|
||||
super::blocking_delay_us(3);
|
||||
}
|
||||
|
||||
/// Read bytes that are readily available in the ring buffer.
|
||||
/// If no bytes are currently available in the buffer the call waits until the some
|
||||
/// bytes are available (at least one byte and at most half the buffer size)
|
||||
///
|
||||
/// Background receive is started if `start()` has not been previously called.
|
||||
///
|
||||
/// Receive in the background is terminated if an error is returned.
|
||||
/// It must then manually be started again by calling `start()` or by re-calling `read()`.
|
||||
pub fn blocking_read<const N: usize>(&mut self, buf: &mut [u16; N]) -> Result<usize, OverrunError> {
|
||||
let r = T::regs();
|
||||
|
||||
// Start background receive if it was not already started
|
||||
if !r.cr2().read().dma() {
|
||||
self.start()?;
|
||||
}
|
||||
|
||||
// Clear overrun flag if set.
|
||||
if r.sr().read().ovr() {
|
||||
return self.stop(OverrunError);
|
||||
}
|
||||
|
||||
loop {
|
||||
match self.ring_buf.read(buf) {
|
||||
Ok((0, _)) => {}
|
||||
Ok((len, _)) => {
|
||||
return Ok(len);
|
||||
}
|
||||
Err(_) => {
|
||||
return self.stop(OverrunError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads measurements from the DMA ring buffer.
|
||||
///
|
||||
/// This method fills the provided `measurements` array with ADC readings from the DMA buffer.
|
||||
/// The length of the `measurements` array should be exactly half of the DMA buffer length. Because interrupts are only generated if half or full DMA transfer completes.
|
||||
///
|
||||
/// Each call to `read` will populate the `measurements` array in the same order as the channels defined with `set_sample_sequence`.
|
||||
/// There will be many sequences worth of measurements in this array because it only returns if at least half of the DMA buffer is filled.
|
||||
/// For example if 3 channels are sampled `measurements` contain: `[sq0 sq1 sq3 sq0 sq1 sq3 sq0 sq1 sq3 sq0 sq1 sq3..]`.
|
||||
///
|
||||
/// If an error is returned, it indicates a DMA overrun, and the process must be restarted by calling `start` or `read` again.
|
||||
///
|
||||
/// By default, the ADC fills the DMA buffer as quickly as possible. To control the sample rate, call `teardown_adc` after each readout, and then start the DMA again at the desired interval.
|
||||
/// Note that even if using `teardown_adc` to control the sample rate, with each call to `read`, measurements equivalent to half the size of the DMA buffer are still collected.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust,ignore
|
||||
/// const DMA_BUF_LEN: usize = 120;
|
||||
/// let adc_dma_buf = [0u16; DMA_BUF_LEN];
|
||||
/// let mut adc: RingBufferedAdc<embassy_stm32::peripherals::ADC1> = adc.into_ring_buffered(p.DMA2_CH0, adc_dma_buf);
|
||||
///
|
||||
/// adc.set_sample_sequence(Sequence::One, &mut p.PA0, SampleTime::CYCLES112);
|
||||
/// adc.set_sample_sequence(Sequence::Two, &mut p.PA1, SampleTime::CYCLES112);
|
||||
/// adc.set_sample_sequence(Sequence::Three, &mut p.PA2, SampleTime::CYCLES112);
|
||||
///
|
||||
/// let mut measurements = [0u16; DMA_BUF_LEN / 2];
|
||||
/// loop {
|
||||
/// match adc.read(&mut measurements).await {
|
||||
/// Ok(_) => {
|
||||
/// defmt::info!("adc1: {}", measurements);
|
||||
/// // Only needed to manually control sample rate.
|
||||
/// adc.teardown_adc();
|
||||
/// }
|
||||
/// Err(e) => {
|
||||
/// defmt::warn!("Error: {:?}", e);
|
||||
/// // DMA overrun, next call to `read` restarts ADC.
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Manually control sample rate.
|
||||
/// Timer::after_millis(100).await;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// [`set_sample_sequence`]: #method.set_sample_sequence
|
||||
/// [`teardown_adc`]: #method.teardown_adc
|
||||
/// [`start`]: #method.start
|
||||
pub async fn read<const N: usize>(&mut self, measurements: &mut [u16; N]) -> Result<usize, OverrunError> {
|
||||
assert_eq!(
|
||||
self.ring_buf.capacity() / 2,
|
||||
N,
|
||||
"Buffer size must be half the size of the ring buffer"
|
||||
);
|
||||
|
||||
let r = T::regs();
|
||||
|
||||
// Start background receive if it was not already started
|
||||
if !r.cr2().read().dma() {
|
||||
self.start()?;
|
||||
}
|
||||
|
||||
// Clear overrun flag if set.
|
||||
if r.sr().read().ovr() {
|
||||
return self.stop(OverrunError);
|
||||
}
|
||||
match self.ring_buf.read_exact(measurements).await {
|
||||
Ok(len) => Ok(len),
|
||||
Err(_) => self.stop(OverrunError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Instance> Drop for RingBufferedAdc<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
self.teardown_adc();
|
||||
rcc::disable::<T>();
|
||||
}
|
||||
}
|
@ -6,6 +6,9 @@ use crate::peripherals::ADC1;
|
||||
use crate::time::Hertz;
|
||||
use crate::{rcc, Peripheral};
|
||||
|
||||
mod ringbuffered_v2;
|
||||
pub use ringbuffered_v2::{RingBufferedAdc, Sequence};
|
||||
|
||||
/// Default VREF voltage used for sample conversion to millivolts.
|
||||
pub const VREF_DEFAULT_MV: u32 = 3300;
|
||||
/// VREF voltage used for factory calibration of VREFINTCAL register.
|
||||
@ -175,7 +178,7 @@ where
|
||||
T::regs().dr().read().0 as u16
|
||||
}
|
||||
|
||||
pub fn read(&mut self, channel: &mut impl AdcChannel<T>) -> u16 {
|
||||
pub fn blocking_read(&mut self, channel: &mut impl AdcChannel<T>) -> u16 {
|
||||
channel.setup();
|
||||
|
||||
// Configure ADC
|
||||
|
@ -1,9 +1,12 @@
|
||||
use cfg_if::cfg_if;
|
||||
use embassy_hal_internal::into_ref;
|
||||
use pac::adc::vals::Dmacfg;
|
||||
|
||||
use super::blocking_delay_us;
|
||||
use crate::adc::{Adc, AdcChannel, Instance, Resolution, SampleTime};
|
||||
use crate::{rcc, Peripheral};
|
||||
use super::{
|
||||
blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel,
|
||||
};
|
||||
use crate::dma::Transfer;
|
||||
use crate::{pac, rcc, Peripheral};
|
||||
|
||||
/// Default VREF voltage used for sample conversion to millivolts.
|
||||
pub const VREF_DEFAULT_MV: u32 = 3300;
|
||||
@ -12,7 +15,7 @@ pub const VREF_CALIB_MV: u32 = 3000;
|
||||
|
||||
pub struct VrefInt;
|
||||
impl<T: Instance> AdcChannel<T> for VrefInt {}
|
||||
impl<T: Instance> super::SealedAdcChannel<T> for VrefInt {
|
||||
impl<T: Instance> SealedAdcChannel<T> for VrefInt {
|
||||
fn channel(&self) -> u8 {
|
||||
cfg_if! {
|
||||
if #[cfg(adc_g0)] {
|
||||
@ -31,7 +34,7 @@ impl<T: Instance> super::SealedAdcChannel<T> for VrefInt {
|
||||
|
||||
pub struct Temperature;
|
||||
impl<T: Instance> AdcChannel<T> for Temperature {}
|
||||
impl<T: Instance> super::SealedAdcChannel<T> for Temperature {
|
||||
impl<T: Instance> SealedAdcChannel<T> for Temperature {
|
||||
fn channel(&self) -> u8 {
|
||||
cfg_if! {
|
||||
if #[cfg(adc_g0)] {
|
||||
@ -50,7 +53,7 @@ impl<T: Instance> super::SealedAdcChannel<T> for Temperature {
|
||||
|
||||
pub struct Vbat;
|
||||
impl<T: Instance> AdcChannel<T> for Vbat {}
|
||||
impl<T: Instance> super::SealedAdcChannel<T> for Vbat {
|
||||
impl<T: Instance> SealedAdcChannel<T> for Vbat {
|
||||
fn channel(&self) -> u8 {
|
||||
cfg_if! {
|
||||
if #[cfg(adc_g0)] {
|
||||
@ -101,6 +104,7 @@ impl<'d, T: Instance> Adc<'d, T> {
|
||||
reg.set_advregen(true);
|
||||
});
|
||||
|
||||
// If this is false then each ADC_CHSELR bit enables an input channel.
|
||||
#[cfg(any(adc_g0, adc_u0))]
|
||||
T::regs().cfgr1().modify(|reg| {
|
||||
reg.set_chselrmod(false);
|
||||
@ -124,6 +128,28 @@ impl<'d, T: Instance> Adc<'d, T> {
|
||||
}
|
||||
}
|
||||
|
||||
// Enable ADC only when it is not already running.
|
||||
fn enable(&mut self) {
|
||||
// Make sure bits are off
|
||||
while T::regs().cr().read().addis() {
|
||||
// spin
|
||||
}
|
||||
|
||||
if !T::regs().cr().read().aden() {
|
||||
// Enable ADC
|
||||
T::regs().isr().modify(|reg| {
|
||||
reg.set_adrdy(true);
|
||||
});
|
||||
T::regs().cr().modify(|reg| {
|
||||
reg.set_aden(true);
|
||||
});
|
||||
|
||||
while !T::regs().isr().read().adrdy() {
|
||||
// spin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_vrefint(&self) -> VrefInt {
|
||||
#[cfg(not(any(adc_g0, adc_u0)))]
|
||||
T::common_regs().ccr().modify(|reg| {
|
||||
@ -181,10 +207,17 @@ impl<'d, T: Instance> Adc<'d, T> {
|
||||
Vbat {}
|
||||
}
|
||||
|
||||
/// Set the ADC sample time.
|
||||
pub fn set_sample_time(&mut self, sample_time: SampleTime) {
|
||||
self.sample_time = sample_time;
|
||||
}
|
||||
|
||||
/// Get the ADC sample time.
|
||||
pub fn sample_time(&self) -> SampleTime {
|
||||
self.sample_time
|
||||
}
|
||||
|
||||
/// Set the ADC resolution.
|
||||
pub fn set_resolution(&mut self, resolution: Resolution) {
|
||||
#[cfg(not(any(adc_g0, adc_u0)))]
|
||||
T::regs().cfgr().modify(|reg| reg.set_res(resolution.into()));
|
||||
@ -220,24 +253,164 @@ impl<'d, T: Instance> Adc<'d, T> {
|
||||
T::regs().dr().read().0 as u16
|
||||
}
|
||||
|
||||
pub fn read(&mut self, channel: &mut impl AdcChannel<T>) -> u16 {
|
||||
// Make sure bits are off
|
||||
while T::regs().cr().read().addis() {
|
||||
// spin
|
||||
/// Read an ADC channel.
|
||||
pub fn blocking_read(&mut self, channel: &mut impl AdcChannel<T>) -> u16 {
|
||||
self.read_channel(channel)
|
||||
}
|
||||
|
||||
/// Read one or multiple ADC channels using DMA.
|
||||
///
|
||||
/// `sequence` iterator and `readings` must have the same length.
|
||||
///
|
||||
/// Example
|
||||
/// ```rust,ignore
|
||||
/// use embassy_stm32::adc::{Adc, AdcChannel}
|
||||
///
|
||||
/// let mut adc = Adc::new(p.ADC1);
|
||||
/// let mut adc_pin0 = p.PA0.degrade_adc();
|
||||
/// let mut adc_pin1 = p.PA1.degrade_adc();
|
||||
/// let mut measurements = [0u16; 2];
|
||||
///
|
||||
/// adc.read_async(
|
||||
/// p.DMA1_CH2,
|
||||
/// [
|
||||
/// (&mut *adc_pin0, SampleTime::CYCLES160_5),
|
||||
/// (&mut *adc_pin1, SampleTime::CYCLES160_5),
|
||||
/// ]
|
||||
/// .into_iter(),
|
||||
/// &mut measurements,
|
||||
/// )
|
||||
/// .await;
|
||||
/// defmt::info!("measurements: {}", measurements);
|
||||
/// ```
|
||||
pub async fn read(
|
||||
&mut self,
|
||||
rx_dma: &mut impl RxDma<T>,
|
||||
sequence: impl ExactSizeIterator<Item = (&mut AnyAdcChannel<T>, SampleTime)>,
|
||||
readings: &mut [u16],
|
||||
) {
|
||||
assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty");
|
||||
assert!(
|
||||
sequence.len() == readings.len(),
|
||||
"Sequence length must be equal to readings length"
|
||||
);
|
||||
assert!(
|
||||
sequence.len() <= 16,
|
||||
"Asynchronous read sequence cannot be more than 16 in length"
|
||||
);
|
||||
|
||||
// Ensure no conversions are ongoing and ADC is enabled.
|
||||
Self::cancel_conversions();
|
||||
self.enable();
|
||||
|
||||
// Set sequence length
|
||||
#[cfg(not(any(adc_g0, adc_u0)))]
|
||||
T::regs().sqr1().modify(|w| {
|
||||
w.set_l(sequence.len() as u8 - 1);
|
||||
});
|
||||
|
||||
#[cfg(any(adc_g0, adc_u0))]
|
||||
let mut channel_mask = 0;
|
||||
|
||||
// Configure channels and ranks
|
||||
for (_i, (channel, sample_time)) in sequence.enumerate() {
|
||||
Self::configure_channel(channel, sample_time);
|
||||
|
||||
// Each channel is sampled according to sequence
|
||||
#[cfg(not(any(adc_g0, adc_u0)))]
|
||||
match _i {
|
||||
0..=3 => {
|
||||
T::regs().sqr1().modify(|w| {
|
||||
w.set_sq(_i, channel.channel());
|
||||
});
|
||||
}
|
||||
4..=8 => {
|
||||
T::regs().sqr2().modify(|w| {
|
||||
w.set_sq(_i - 4, channel.channel());
|
||||
});
|
||||
}
|
||||
9..=13 => {
|
||||
T::regs().sqr3().modify(|w| {
|
||||
w.set_sq(_i - 9, channel.channel());
|
||||
});
|
||||
}
|
||||
14..=15 => {
|
||||
T::regs().sqr4().modify(|w| {
|
||||
w.set_sq(_i - 14, channel.channel());
|
||||
});
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
#[cfg(any(adc_g0, adc_u0))]
|
||||
{
|
||||
channel_mask |= 1 << channel.channel();
|
||||
}
|
||||
}
|
||||
|
||||
// Enable ADC
|
||||
// On G0 and U0 enabled channels are sampled from 0 to last channel.
|
||||
// It is possible to add up to 8 sequences if CHSELRMOD = 1.
|
||||
// However for supporting more than 8 channels alternative CHSELRMOD = 0 approach is used.
|
||||
#[cfg(any(adc_g0, adc_u0))]
|
||||
T::regs().chselr().modify(|reg| {
|
||||
reg.set_chsel(channel_mask);
|
||||
});
|
||||
|
||||
// Set continuous mode with oneshot dma.
|
||||
// Clear overrun flag before starting transfer.
|
||||
T::regs().isr().modify(|reg| {
|
||||
reg.set_adrdy(true);
|
||||
reg.set_ovr(true);
|
||||
});
|
||||
|
||||
#[cfg(not(any(adc_g0, adc_u0)))]
|
||||
T::regs().cfgr().modify(|reg| {
|
||||
reg.set_discen(false);
|
||||
reg.set_cont(true);
|
||||
reg.set_dmacfg(Dmacfg::ONESHOT);
|
||||
reg.set_dmaen(true);
|
||||
});
|
||||
#[cfg(any(adc_g0, adc_u0))]
|
||||
T::regs().cfgr1().modify(|reg| {
|
||||
reg.set_discen(false);
|
||||
reg.set_cont(true);
|
||||
reg.set_dmacfg(Dmacfg::ONESHOT);
|
||||
reg.set_dmaen(true);
|
||||
});
|
||||
|
||||
let request = rx_dma.request();
|
||||
let transfer = unsafe {
|
||||
Transfer::new_read(
|
||||
rx_dma,
|
||||
request,
|
||||
T::regs().dr().as_ptr() as *mut u16,
|
||||
readings,
|
||||
Default::default(),
|
||||
)
|
||||
};
|
||||
|
||||
// Start conversion
|
||||
T::regs().cr().modify(|reg| {
|
||||
reg.set_aden(true);
|
||||
reg.set_adstart(true);
|
||||
});
|
||||
|
||||
while !T::regs().isr().read().adrdy() {
|
||||
// spin
|
||||
}
|
||||
// Wait for conversion sequence to finish.
|
||||
transfer.await;
|
||||
|
||||
// Ensure conversions are finished.
|
||||
Self::cancel_conversions();
|
||||
|
||||
// Reset configuration.
|
||||
#[cfg(not(any(adc_g0, adc_u0)))]
|
||||
T::regs().cfgr().modify(|reg| {
|
||||
reg.set_cont(false);
|
||||
});
|
||||
#[cfg(any(adc_g0, adc_u0))]
|
||||
T::regs().cfgr1().modify(|reg| {
|
||||
reg.set_cont(false);
|
||||
});
|
||||
}
|
||||
|
||||
fn configure_channel(channel: &mut impl AdcChannel<T>, sample_time: SampleTime) {
|
||||
// RM0492, RM0481, etc.
|
||||
// "This option bit must be set to 1 when ADCx_INP0 or ADCx_INN1 channel is selected."
|
||||
#[cfg(adc_h5)]
|
||||
@ -246,7 +419,12 @@ impl<'d, T: Instance> Adc<'d, T> {
|
||||
}
|
||||
|
||||
// Configure channel
|
||||
Self::set_channel_sample_time(channel.channel(), self.sample_time);
|
||||
Self::set_channel_sample_time(channel.channel(), sample_time);
|
||||
}
|
||||
|
||||
fn read_channel(&mut self, channel: &mut impl AdcChannel<T>) -> u16 {
|
||||
self.enable();
|
||||
Self::configure_channel(channel, self.sample_time);
|
||||
|
||||
// Select channel
|
||||
#[cfg(not(any(adc_g0, adc_u0)))]
|
||||
@ -262,7 +440,6 @@ impl<'d, T: Instance> Adc<'d, T> {
|
||||
// STM32G4: Section 2.7.3
|
||||
#[cfg(any(rcc_l4, rcc_g4))]
|
||||
let _ = self.convert();
|
||||
|
||||
let val = self.convert();
|
||||
|
||||
T::regs().cr().modify(|reg| reg.set_addis(true));
|
||||
@ -277,9 +454,25 @@ impl<'d, T: Instance> Adc<'d, T> {
|
||||
val
|
||||
}
|
||||
|
||||
#[cfg(any(adc_g0, adc_u0))]
|
||||
pub fn set_oversampling_shift(&mut self, shift: u8) {
|
||||
T::regs().cfgr2().modify(|reg| reg.set_ovss(shift));
|
||||
}
|
||||
|
||||
#[cfg(any(adc_g0, adc_u0))]
|
||||
pub fn set_oversampling_ratio(&mut self, ratio: u8) {
|
||||
T::regs().cfgr2().modify(|reg| reg.set_ovsr(ratio));
|
||||
}
|
||||
|
||||
#[cfg(any(adc_g0, adc_u0))]
|
||||
pub fn oversampling_enable(&mut self, enable: bool) {
|
||||
T::regs().cfgr2().modify(|reg| reg.set_ovse(enable));
|
||||
}
|
||||
|
||||
fn set_channel_sample_time(_ch: u8, sample_time: SampleTime) {
|
||||
cfg_if! {
|
||||
if #[cfg(any(adc_g0, adc_u0))] {
|
||||
// On G0 and U6 all channels use the same sampling time.
|
||||
T::regs().smpr().modify(|reg| reg.set_smp1(sample_time.into()));
|
||||
} else if #[cfg(adc_h5)] {
|
||||
match _ch {
|
||||
@ -294,4 +487,13 @@ impl<'d, T: Instance> Adc<'d, T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_conversions() {
|
||||
if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() {
|
||||
T::regs().cr().modify(|reg| {
|
||||
reg.set_adstp(true);
|
||||
});
|
||||
while T::regs().cr().read().adstart() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
#[allow(unused)]
|
||||
use pac::adc::vals::{Adcaldif, Boost, Difsel, Exten, Pcsel};
|
||||
use pac::adc::vals::{Adcaldif, Adstp, Boost, Difsel, Dmngt, Exten, Pcsel};
|
||||
use pac::adccommon::vals::Presc;
|
||||
|
||||
use super::{blocking_delay_us, Adc, AdcChannel, Instance, Resolution, SampleTime};
|
||||
use super::{
|
||||
blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel,
|
||||
};
|
||||
use crate::dma::Transfer;
|
||||
use crate::time::Hertz;
|
||||
use crate::{pac, rcc, Peripheral};
|
||||
|
||||
@ -34,7 +37,7 @@ const VBAT_CHANNEL: u8 = 17;
|
||||
/// Internal voltage reference channel.
|
||||
pub struct VrefInt;
|
||||
impl<T: Instance> AdcChannel<T> for VrefInt {}
|
||||
impl<T: Instance> super::SealedAdcChannel<T> for VrefInt {
|
||||
impl<T: Instance> SealedAdcChannel<T> for VrefInt {
|
||||
fn channel(&self) -> u8 {
|
||||
VREF_CHANNEL
|
||||
}
|
||||
@ -43,7 +46,7 @@ impl<T: Instance> super::SealedAdcChannel<T> for VrefInt {
|
||||
/// Internal temperature channel.
|
||||
pub struct Temperature;
|
||||
impl<T: Instance> AdcChannel<T> for Temperature {}
|
||||
impl<T: Instance> super::SealedAdcChannel<T> for Temperature {
|
||||
impl<T: Instance> SealedAdcChannel<T> for Temperature {
|
||||
fn channel(&self) -> u8 {
|
||||
TEMP_CHANNEL
|
||||
}
|
||||
@ -52,7 +55,7 @@ impl<T: Instance> super::SealedAdcChannel<T> for Temperature {
|
||||
/// Internal battery voltage channel.
|
||||
pub struct Vbat;
|
||||
impl<T: Instance> AdcChannel<T> for Vbat {}
|
||||
impl<T: Instance> super::SealedAdcChannel<T> for Vbat {
|
||||
impl<T: Instance> SealedAdcChannel<T> for Vbat {
|
||||
fn channel(&self) -> u8 {
|
||||
VBAT_CHANNEL
|
||||
}
|
||||
@ -126,6 +129,21 @@ impl Prescaler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of samples used for averaging.
|
||||
pub enum Averaging {
|
||||
Disabled,
|
||||
Samples2,
|
||||
Samples4,
|
||||
Samples8,
|
||||
Samples16,
|
||||
Samples32,
|
||||
Samples64,
|
||||
Samples128,
|
||||
Samples256,
|
||||
Samples512,
|
||||
Samples1024,
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> Adc<'d, T> {
|
||||
/// Create a new ADC driver.
|
||||
pub fn new(adc: impl Peripheral<P = T> + 'd) -> Self {
|
||||
@ -247,11 +265,39 @@ impl<'d, T: Instance> Adc<'d, T> {
|
||||
self.sample_time = sample_time;
|
||||
}
|
||||
|
||||
/// Get the ADC sample time.
|
||||
pub fn sample_time(&self) -> SampleTime {
|
||||
self.sample_time
|
||||
}
|
||||
|
||||
/// Set the ADC resolution.
|
||||
pub fn set_resolution(&mut self, resolution: Resolution) {
|
||||
T::regs().cfgr().modify(|reg| reg.set_res(resolution.into()));
|
||||
}
|
||||
|
||||
/// Set hardware averaging.
|
||||
pub fn set_averaging(&mut self, averaging: Averaging) {
|
||||
let (enable, samples, right_shift) = match averaging {
|
||||
Averaging::Disabled => (false, 0, 0),
|
||||
Averaging::Samples2 => (true, 1, 1),
|
||||
Averaging::Samples4 => (true, 3, 2),
|
||||
Averaging::Samples8 => (true, 7, 3),
|
||||
Averaging::Samples16 => (true, 15, 4),
|
||||
Averaging::Samples32 => (true, 31, 5),
|
||||
Averaging::Samples64 => (true, 63, 6),
|
||||
Averaging::Samples128 => (true, 127, 7),
|
||||
Averaging::Samples256 => (true, 255, 8),
|
||||
Averaging::Samples512 => (true, 511, 9),
|
||||
Averaging::Samples1024 => (true, 1023, 10),
|
||||
};
|
||||
|
||||
T::regs().cfgr2().modify(|reg| {
|
||||
reg.set_rovse(enable);
|
||||
reg.set_osvr(samples);
|
||||
reg.set_ovss(right_shift);
|
||||
})
|
||||
}
|
||||
|
||||
/// Perform a single conversion.
|
||||
fn convert(&mut self) -> u16 {
|
||||
T::regs().isr().modify(|reg| {
|
||||
@ -272,26 +318,148 @@ impl<'d, T: Instance> Adc<'d, T> {
|
||||
}
|
||||
|
||||
/// Read an ADC channel.
|
||||
pub fn read(&mut self, channel: &mut impl AdcChannel<T>) -> u16 {
|
||||
channel.setup();
|
||||
|
||||
self.read_channel(channel.channel())
|
||||
pub fn blocking_read(&mut self, channel: &mut impl AdcChannel<T>) -> u16 {
|
||||
self.read_channel(channel)
|
||||
}
|
||||
|
||||
fn read_channel(&mut self, channel: u8) -> u16 {
|
||||
// Configure channel
|
||||
Self::set_channel_sample_time(channel, self.sample_time);
|
||||
/// Read one or multiple ADC channels using DMA.
|
||||
///
|
||||
/// `sequence` iterator and `readings` must have the same length.
|
||||
///
|
||||
/// Example
|
||||
/// ```rust,ignore
|
||||
/// use embassy_stm32::adc::{Adc, AdcChannel}
|
||||
///
|
||||
/// let mut adc = Adc::new(p.ADC1);
|
||||
/// let mut adc_pin0 = p.PA0.degrade_adc();
|
||||
/// let mut adc_pin2 = p.PA2.degrade_adc();
|
||||
/// let mut measurements = [0u16; 2];
|
||||
///
|
||||
/// adc.read_async(
|
||||
/// p.DMA2_CH0,
|
||||
/// [
|
||||
/// (&mut *adc_pin0, SampleTime::CYCLES112),
|
||||
/// (&mut *adc_pin2, SampleTime::CYCLES112),
|
||||
/// ]
|
||||
/// .into_iter(),
|
||||
/// &mut measurements,
|
||||
/// )
|
||||
/// .await;
|
||||
/// defmt::info!("measurements: {}", measurements);
|
||||
/// ```
|
||||
pub async fn read(
|
||||
&mut self,
|
||||
rx_dma: &mut impl RxDma<T>,
|
||||
sequence: impl ExactSizeIterator<Item = (&mut AnyAdcChannel<T>, SampleTime)>,
|
||||
readings: &mut [u16],
|
||||
) {
|
||||
assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty");
|
||||
assert!(
|
||||
sequence.len() == readings.len(),
|
||||
"Sequence length must be equal to readings length"
|
||||
);
|
||||
assert!(
|
||||
sequence.len() <= 16,
|
||||
"Asynchronous read sequence cannot be more than 16 in length"
|
||||
);
|
||||
|
||||
// Ensure no conversions are ongoing
|
||||
Self::cancel_conversions();
|
||||
|
||||
// Set sequence length
|
||||
T::regs().sqr1().modify(|w| {
|
||||
w.set_l(sequence.len() as u8 - 1);
|
||||
});
|
||||
|
||||
// Configure channels and ranks
|
||||
for (i, (channel, sample_time)) in sequence.enumerate() {
|
||||
Self::configure_channel(channel, sample_time);
|
||||
match i {
|
||||
0..=3 => {
|
||||
T::regs().sqr1().modify(|w| {
|
||||
w.set_sq(i, channel.channel());
|
||||
});
|
||||
}
|
||||
4..=8 => {
|
||||
T::regs().sqr2().modify(|w| {
|
||||
w.set_sq(i - 4, channel.channel());
|
||||
});
|
||||
}
|
||||
9..=13 => {
|
||||
T::regs().sqr3().modify(|w| {
|
||||
w.set_sq(i - 9, channel.channel());
|
||||
});
|
||||
}
|
||||
14..=15 => {
|
||||
T::regs().sqr4().modify(|w| {
|
||||
w.set_sq(i - 14, channel.channel());
|
||||
});
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// Set continuous mode with oneshot dma.
|
||||
// Clear overrun flag before starting transfer.
|
||||
|
||||
T::regs().isr().modify(|reg| {
|
||||
reg.set_ovr(true);
|
||||
});
|
||||
T::regs().cfgr().modify(|reg| {
|
||||
reg.set_cont(true);
|
||||
reg.set_dmngt(Dmngt::DMA_ONESHOT);
|
||||
});
|
||||
|
||||
let request = rx_dma.request();
|
||||
let transfer = unsafe {
|
||||
Transfer::new_read(
|
||||
rx_dma,
|
||||
request,
|
||||
T::regs().dr().as_ptr() as *mut u16,
|
||||
readings,
|
||||
Default::default(),
|
||||
)
|
||||
};
|
||||
|
||||
// Start conversion
|
||||
T::regs().cr().modify(|reg| {
|
||||
reg.set_adstart(true);
|
||||
});
|
||||
|
||||
// Wait for conversion sequence to finish.
|
||||
transfer.await;
|
||||
|
||||
// Ensure conversions are finished.
|
||||
Self::cancel_conversions();
|
||||
|
||||
// Reset configuration.
|
||||
T::regs().cfgr().modify(|reg| {
|
||||
reg.set_cont(false);
|
||||
reg.set_dmngt(Dmngt::from_bits(0));
|
||||
});
|
||||
}
|
||||
|
||||
fn configure_channel(channel: &mut impl AdcChannel<T>, sample_time: SampleTime) {
|
||||
channel.setup();
|
||||
|
||||
let channel = channel.channel();
|
||||
|
||||
Self::set_channel_sample_time(channel, sample_time);
|
||||
|
||||
#[cfg(stm32h7)]
|
||||
{
|
||||
T::regs().cfgr2().modify(|w| w.set_lshift(0));
|
||||
T::regs()
|
||||
.pcsel()
|
||||
.write(|w| w.set_pcsel(channel as _, Pcsel::PRESELECTED));
|
||||
.modify(|w| w.set_pcsel(channel as _, Pcsel::PRESELECTED));
|
||||
}
|
||||
}
|
||||
|
||||
T::regs().sqr1().write(|reg| {
|
||||
reg.set_sq(0, channel);
|
||||
fn read_channel(&mut self, channel: &mut impl AdcChannel<T>) -> u16 {
|
||||
Self::configure_channel(channel, self.sample_time);
|
||||
|
||||
T::regs().sqr1().modify(|reg| {
|
||||
reg.set_sq(0, channel.channel());
|
||||
reg.set_l(0);
|
||||
});
|
||||
|
||||
@ -306,4 +474,13 @@ impl<'d, T: Instance> Adc<'d, T> {
|
||||
T::regs().smpr(1).modify(|reg| reg.set_smp((ch - 10) as _, sample_time));
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_conversions() {
|
||||
if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() {
|
||||
T::regs().cr().modify(|reg| {
|
||||
reg.set_adstp(Adstp::STOP);
|
||||
});
|
||||
while T::regs().cr().read().adstart() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -904,6 +904,7 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> {
|
||||
let data_size = W::size();
|
||||
let buffer_ptr = buffer.as_mut_ptr();
|
||||
|
||||
options.half_transfer_ir = true;
|
||||
options.complete_transfer_ir = true;
|
||||
options.circular = true;
|
||||
|
||||
|
@ -109,6 +109,11 @@ impl<'d, M: Mode> I2c<'d, M> {
|
||||
timeout.check()?;
|
||||
}
|
||||
|
||||
// Wait for the bus to be free
|
||||
while info.regs.isr().read().busy() {
|
||||
timeout.check()?;
|
||||
}
|
||||
|
||||
let reload = if reload {
|
||||
i2c::vals::Reload::NOTCOMPLETED
|
||||
} else {
|
||||
|
@ -1,19 +1,194 @@
|
||||
//! LTDC
|
||||
use core::marker::PhantomData;
|
||||
//! LTDC - LCD-TFT Display Controller
|
||||
//! See ST application note AN4861: Introduction to LCD-TFT display controller (LTDC) on STM32 MCUs for high level details
|
||||
//! This module was tested against the stm32h735g-dk using the RM0468 ST reference manual for detailed register information
|
||||
|
||||
use crate::rcc::{self, RccPeripheral};
|
||||
use crate::{peripherals, Peripheral};
|
||||
use core::future::poll_fn;
|
||||
use core::marker::PhantomData;
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use stm32_metapac::ltdc::regs::Dccr;
|
||||
use stm32_metapac::ltdc::vals::{Bf1, Bf2, Cfuif, Clif, Crrif, Cterrif, Pf, Vbr};
|
||||
|
||||
use crate::gpio::{AfType, OutputType, Speed};
|
||||
use crate::interrupt::typelevel::Interrupt;
|
||||
use crate::interrupt::{self};
|
||||
use crate::{peripherals, rcc, Peripheral};
|
||||
|
||||
static LTDC_WAKER: AtomicWaker = AtomicWaker::new();
|
||||
|
||||
/// LTDC error
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Error {
|
||||
/// FIFO underrun. Generated when a pixel is requested while the FIFO is empty
|
||||
FifoUnderrun,
|
||||
/// Transfer error. Generated when a bus error occurs
|
||||
TransferError,
|
||||
}
|
||||
|
||||
/// Display configuration parameters
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct LtdcConfiguration {
|
||||
/// Active width in pixels
|
||||
pub active_width: u16,
|
||||
/// Active height in pixels
|
||||
pub active_height: u16,
|
||||
|
||||
/// Horizontal back porch (in units of pixel clock period)
|
||||
pub h_back_porch: u16,
|
||||
/// Horizontal front porch (in units of pixel clock period)
|
||||
pub h_front_porch: u16,
|
||||
/// Vertical back porch (in units of horizontal scan line)
|
||||
pub v_back_porch: u16,
|
||||
/// Vertical front porch (in units of horizontal scan line)
|
||||
pub v_front_porch: u16,
|
||||
|
||||
/// Horizontal synchronization width (in units of pixel clock period)
|
||||
pub h_sync: u16,
|
||||
/// Vertical synchronization height (in units of horizontal scan line)
|
||||
pub v_sync: u16,
|
||||
|
||||
/// Horizontal synchronization polarity: `false`: active low, `true`: active high
|
||||
pub h_sync_polarity: PolarityActive,
|
||||
/// Vertical synchronization polarity: `false`: active low, `true`: active high
|
||||
pub v_sync_polarity: PolarityActive,
|
||||
/// Data enable polarity: `false`: active low, `true`: active high
|
||||
pub data_enable_polarity: PolarityActive,
|
||||
/// Pixel clock polarity: `false`: falling edge, `true`: rising edge
|
||||
pub pixel_clock_polarity: PolarityEdge,
|
||||
}
|
||||
|
||||
/// Edge polarity
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PolarityEdge {
|
||||
/// Falling edge
|
||||
FallingEdge,
|
||||
/// Rising edge
|
||||
RisingEdge,
|
||||
}
|
||||
|
||||
/// Active polarity
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PolarityActive {
|
||||
/// Active low
|
||||
ActiveLow,
|
||||
/// Active high
|
||||
ActiveHigh,
|
||||
}
|
||||
|
||||
/// LTDC driver.
|
||||
pub struct Ltdc<'d, T: Instance> {
|
||||
_peri: PhantomData<&'d mut T>,
|
||||
_peri: PeripheralRef<'d, T>,
|
||||
}
|
||||
|
||||
/// LTDC interrupt handler.
|
||||
pub struct InterruptHandler<T: Instance> {
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
/// 24 bit color
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct RgbColor {
|
||||
/// Red
|
||||
pub red: u8,
|
||||
/// Green
|
||||
pub green: u8,
|
||||
/// Blue
|
||||
pub blue: u8,
|
||||
}
|
||||
|
||||
/// Layer
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct LtdcLayerConfig {
|
||||
/// Layer number
|
||||
pub layer: LtdcLayer,
|
||||
/// Pixel format
|
||||
pub pixel_format: PixelFormat,
|
||||
/// Window left x in pixels
|
||||
pub window_x0: u16,
|
||||
/// Window right x in pixels
|
||||
pub window_x1: u16,
|
||||
/// Window top y in pixels
|
||||
pub window_y0: u16,
|
||||
/// Window bottom y in pixels
|
||||
pub window_y1: u16,
|
||||
}
|
||||
|
||||
/// Pixel format
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PixelFormat {
|
||||
/// ARGB8888
|
||||
ARGB8888 = Pf::ARGB8888 as u8,
|
||||
/// RGB888
|
||||
RGB888 = Pf::RGB888 as u8,
|
||||
/// RGB565
|
||||
RGB565 = Pf::RGB565 as u8,
|
||||
/// ARGB1555
|
||||
ARGB1555 = Pf::ARGB1555 as u8,
|
||||
/// ARGB4444
|
||||
ARGB4444 = Pf::ARGB4444 as u8,
|
||||
/// L8 (8-bit luminance)
|
||||
L8 = Pf::L8 as u8,
|
||||
/// AL44 (4-bit alpha, 4-bit luminance
|
||||
AL44 = Pf::AL44 as u8,
|
||||
/// AL88 (8-bit alpha, 8-bit luminance)
|
||||
AL88 = Pf::AL88 as u8,
|
||||
}
|
||||
|
||||
impl PixelFormat {
|
||||
/// Number of bytes per pixel
|
||||
pub fn bytes_per_pixel(&self) -> usize {
|
||||
match self {
|
||||
PixelFormat::ARGB8888 => 4,
|
||||
PixelFormat::RGB888 => 3,
|
||||
PixelFormat::RGB565 | PixelFormat::ARGB4444 | PixelFormat::ARGB1555 | PixelFormat::AL88 => 2,
|
||||
PixelFormat::AL44 | PixelFormat::L8 => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Ltdc Blending Layer
|
||||
#[repr(usize)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum LtdcLayer {
|
||||
/// Bottom layer
|
||||
Layer1 = 0,
|
||||
/// Top layer
|
||||
Layer2 = 1,
|
||||
}
|
||||
|
||||
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
|
||||
unsafe fn on_interrupt() {
|
||||
cortex_m::asm::dsb();
|
||||
Ltdc::<T>::enable_interrupts(false);
|
||||
LTDC_WAKER.wake();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> Ltdc<'d, T> {
|
||||
// Create a new LTDC driver without specifying color and control pins. This is typically used if you want to drive a display though a DsiHost
|
||||
/// Note: Full-Duplex modes are not supported at this time
|
||||
pub fn new(
|
||||
_peri: impl Peripheral<P = T> + 'd,
|
||||
/*
|
||||
pub fn new(peri: impl Peripheral<P = T> + 'd) -> Self {
|
||||
Self::setup_clocks();
|
||||
into_ref!(peri);
|
||||
Self { _peri: peri }
|
||||
}
|
||||
|
||||
/// Create a new LTDC driver. 8 pins per color channel for blue, green and red
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new_with_pins(
|
||||
peri: impl Peripheral<P = T> + 'd,
|
||||
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
|
||||
clk: impl Peripheral<P = impl ClkPin<T>> + 'd,
|
||||
hsync: impl Peripheral<P = impl HsyncPin<T>> + 'd,
|
||||
vsync: impl Peripheral<P = impl VsyncPin<T>> + 'd,
|
||||
@ -41,40 +216,112 @@ impl<'d, T: Instance> Ltdc<'d, T> {
|
||||
r5: impl Peripheral<P = impl R5Pin<T>> + 'd,
|
||||
r6: impl Peripheral<P = impl R6Pin<T>> + 'd,
|
||||
r7: impl Peripheral<P = impl R7Pin<T>> + 'd,
|
||||
*/
|
||||
) -> Self {
|
||||
//into_ref!(clk);
|
||||
Self::setup_clocks();
|
||||
into_ref!(peri);
|
||||
new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(hsync, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(vsync, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(b0, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(b1, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(b2, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(b3, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(b4, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(b5, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(b6, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(b7, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(g0, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(g1, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(g2, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(g3, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(g4, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(g5, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(g6, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(g7, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(r0, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(r1, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(r2, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(r3, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(r4, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(r5, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(r6, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
new_pin!(r7, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
|
||||
critical_section::with(|_cs| {
|
||||
// RM says the pllsaidivr should only be changed when pllsai is off. But this could have other unintended side effects. So let's just give it a try like this.
|
||||
// According to the debugger, this bit gets set, anyway.
|
||||
#[cfg(stm32f7)]
|
||||
stm32_metapac::RCC
|
||||
.dckcfgr1()
|
||||
.modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2));
|
||||
Self { _peri: peri }
|
||||
}
|
||||
|
||||
// It is set to RCC_PLLSAIDIVR_2 in ST's BSP example for the STM32469I-DISCO.
|
||||
#[cfg(not(any(stm32f7, stm32u5)))]
|
||||
stm32_metapac::RCC
|
||||
.dckcfgr()
|
||||
.modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2));
|
||||
/// Initialise and enable the display
|
||||
pub fn init(&mut self, config: &LtdcConfiguration) {
|
||||
use stm32_metapac::ltdc::vals::{Depol, Hspol, Pcpol, Vspol};
|
||||
let ltdc = T::regs();
|
||||
|
||||
// check bus access
|
||||
assert!(ltdc.gcr().read().0 == 0x2220); // reset value
|
||||
|
||||
// configure the HS, VS, DE and PC polarity
|
||||
ltdc.gcr().modify(|w| {
|
||||
w.set_hspol(match config.h_sync_polarity {
|
||||
PolarityActive::ActiveHigh => Hspol::ACTIVEHIGH,
|
||||
PolarityActive::ActiveLow => Hspol::ACTIVELOW,
|
||||
});
|
||||
|
||||
w.set_vspol(match config.v_sync_polarity {
|
||||
PolarityActive::ActiveHigh => Vspol::ACTIVEHIGH,
|
||||
PolarityActive::ActiveLow => Vspol::ACTIVELOW,
|
||||
});
|
||||
|
||||
w.set_depol(match config.data_enable_polarity {
|
||||
PolarityActive::ActiveHigh => Depol::ACTIVEHIGH,
|
||||
PolarityActive::ActiveLow => Depol::ACTIVELOW,
|
||||
});
|
||||
|
||||
w.set_pcpol(match config.pixel_clock_polarity {
|
||||
PolarityEdge::RisingEdge => Pcpol::RISINGEDGE,
|
||||
PolarityEdge::FallingEdge => Pcpol::FALLINGEDGE,
|
||||
});
|
||||
});
|
||||
|
||||
rcc::enable_and_reset::<T>();
|
||||
// set synchronization pulse width
|
||||
ltdc.sscr().modify(|w| {
|
||||
w.set_vsh(config.v_sync - 1);
|
||||
w.set_hsw(config.h_sync - 1);
|
||||
});
|
||||
|
||||
//new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||
// set accumulated back porch
|
||||
ltdc.bpcr().modify(|w| {
|
||||
w.set_avbp(config.v_sync + config.v_back_porch - 1);
|
||||
w.set_ahbp(config.h_sync + config.h_back_porch - 1);
|
||||
});
|
||||
|
||||
// Set Tearing Enable pin according to CubeMx example
|
||||
//te.set_as_af_pull(te.af_num(), AfType::output(OutputType::PushPull, Speed::Low));
|
||||
/*
|
||||
T::regs().wcr().modify(|w| {
|
||||
w.set_dsien(true);
|
||||
});
|
||||
*/
|
||||
Self { _peri: PhantomData }
|
||||
// set accumulated active width
|
||||
let aa_height = config.v_sync + config.v_back_porch + config.active_height - 1;
|
||||
let aa_width = config.h_sync + config.h_back_porch + config.active_width - 1;
|
||||
ltdc.awcr().modify(|w| {
|
||||
w.set_aah(aa_height);
|
||||
w.set_aaw(aa_width);
|
||||
});
|
||||
|
||||
// set total width and height
|
||||
let total_height: u16 = config.v_sync + config.v_back_porch + config.active_height + config.v_front_porch - 1;
|
||||
let total_width: u16 = config.h_sync + config.h_back_porch + config.active_width + config.h_front_porch - 1;
|
||||
ltdc.twcr().modify(|w| {
|
||||
w.set_totalh(total_height);
|
||||
w.set_totalw(total_width)
|
||||
});
|
||||
|
||||
// set the background color value to black
|
||||
ltdc.bccr().modify(|w| {
|
||||
w.set_bcred(0);
|
||||
w.set_bcgreen(0);
|
||||
w.set_bcblue(0);
|
||||
});
|
||||
|
||||
self.enable();
|
||||
}
|
||||
|
||||
/// Set the enable bit in the control register and assert that it has been enabled
|
||||
///
|
||||
/// This does need to be called if init has already been called
|
||||
pub fn enable(&mut self) {
|
||||
T::regs().gcr().modify(|w| w.set_ltdcen(true));
|
||||
assert!(T::regs().gcr().read().ltdcen())
|
||||
@ -85,6 +332,188 @@ impl<'d, T: Instance> Ltdc<'d, T> {
|
||||
T::regs().gcr().modify(|w| w.set_ltdcen(false));
|
||||
assert!(!T::regs().gcr().read().ltdcen())
|
||||
}
|
||||
|
||||
/// Initialise and enable the layer
|
||||
///
|
||||
/// clut - a 256 length color look-up table applies to L8, AL44 and AL88 pixel format and will default to greyscale if `None` supplied and these pixel formats are used
|
||||
pub fn init_layer(&mut self, layer_config: &LtdcLayerConfig, clut: Option<&[RgbColor]>) {
|
||||
let ltdc = T::regs();
|
||||
let layer = ltdc.layer(layer_config.layer as usize);
|
||||
|
||||
// 256 color look-up table for L8, AL88 and AL88 pixel formats
|
||||
if let Some(clut) = clut {
|
||||
assert_eq!(clut.len(), 256, "Color lookup table must be exactly 256 in length");
|
||||
for (index, color) in clut.iter().enumerate() {
|
||||
layer.clutwr().write(|w| {
|
||||
w.set_clutadd(index as u8);
|
||||
w.set_red(color.red);
|
||||
w.set_green(color.green);
|
||||
w.set_blue(color.blue);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// configure the horizontal start and stop position
|
||||
let h_win_start = layer_config.window_x0 + ltdc.bpcr().read().ahbp() + 1;
|
||||
let h_win_stop = layer_config.window_x1 + ltdc.bpcr().read().ahbp();
|
||||
layer.whpcr().write(|w| {
|
||||
w.set_whstpos(h_win_start);
|
||||
w.set_whsppos(h_win_stop);
|
||||
});
|
||||
|
||||
// configure the vertical start and stop position
|
||||
let v_win_start = layer_config.window_y0 + ltdc.bpcr().read().avbp() + 1;
|
||||
let v_win_stop = layer_config.window_y1 + ltdc.bpcr().read().avbp();
|
||||
layer.wvpcr().write(|w| {
|
||||
w.set_wvstpos(v_win_start);
|
||||
w.set_wvsppos(v_win_stop)
|
||||
});
|
||||
|
||||
// set the pixel format
|
||||
layer
|
||||
.pfcr()
|
||||
.write(|w| w.set_pf(Pf::from_bits(layer_config.pixel_format as u8)));
|
||||
|
||||
// set the default color value to transparent black
|
||||
layer.dccr().write_value(Dccr::default());
|
||||
|
||||
// set the global constant alpha value
|
||||
let alpha = 0xFF;
|
||||
layer.cacr().write(|w| w.set_consta(alpha));
|
||||
|
||||
// set the blending factors.
|
||||
layer.bfcr().modify(|w| {
|
||||
w.set_bf1(Bf1::PIXEL);
|
||||
w.set_bf2(Bf2::PIXEL);
|
||||
});
|
||||
|
||||
// calculate framebuffer pixel size in bytes
|
||||
let bytes_per_pixel = layer_config.pixel_format.bytes_per_pixel() as u16;
|
||||
let width = layer_config.window_x1 - layer_config.window_x0;
|
||||
let height = layer_config.window_y1 - layer_config.window_y0;
|
||||
|
||||
// framebuffer pitch and line length
|
||||
layer.cfblr().modify(|w| {
|
||||
w.set_cfbp(width * bytes_per_pixel);
|
||||
w.set_cfbll(width * bytes_per_pixel + 7);
|
||||
});
|
||||
|
||||
// framebuffer line number
|
||||
layer.cfblnr().modify(|w| w.set_cfblnbr(height));
|
||||
|
||||
// enable LTDC_Layer by setting LEN bit
|
||||
layer.cr().modify(|w| {
|
||||
if clut.is_some() {
|
||||
w.set_cluten(true);
|
||||
}
|
||||
w.set_len(true);
|
||||
});
|
||||
}
|
||||
|
||||
/// Set the current buffer. The async function will return when buffer has been completely copied to the LCD screen
|
||||
/// frame_buffer_addr is a pointer to memory that should not move (best to make it static)
|
||||
pub async fn set_buffer(&mut self, layer: LtdcLayer, frame_buffer_addr: *const ()) -> Result<(), Error> {
|
||||
let mut bits = T::regs().isr().read();
|
||||
|
||||
// if all clear
|
||||
if !bits.fuif() && !bits.lif() && !bits.rrif() && !bits.terrif() {
|
||||
// wait for interrupt
|
||||
poll_fn(|cx| {
|
||||
// quick check to avoid registration if already done.
|
||||
let bits = T::regs().isr().read();
|
||||
if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() {
|
||||
return Poll::Ready(());
|
||||
}
|
||||
|
||||
LTDC_WAKER.register(cx.waker());
|
||||
Self::clear_interrupt_flags(); // don't poison the request with old flags
|
||||
Self::enable_interrupts(true);
|
||||
|
||||
// set the new frame buffer address
|
||||
let layer = T::regs().layer(layer as usize);
|
||||
layer.cfbar().modify(|w| w.set_cfbadd(frame_buffer_addr as u32));
|
||||
|
||||
// configure a shadow reload for the next blanking period
|
||||
T::regs().srcr().write(|w| {
|
||||
w.set_vbr(Vbr::RELOAD);
|
||||
});
|
||||
|
||||
// need to check condition after register to avoid a race
|
||||
// condition that would result in lost notifications.
|
||||
let bits = T::regs().isr().read();
|
||||
if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
// re-read the status register after wait.
|
||||
bits = T::regs().isr().read();
|
||||
}
|
||||
|
||||
let result = if bits.fuif() {
|
||||
Err(Error::FifoUnderrun)
|
||||
} else if bits.terrif() {
|
||||
Err(Error::TransferError)
|
||||
} else if bits.lif() {
|
||||
panic!("line interrupt event is disabled")
|
||||
} else if bits.rrif() {
|
||||
// register reload flag is expected
|
||||
Ok(())
|
||||
} else {
|
||||
unreachable!("all interrupt status values checked")
|
||||
};
|
||||
|
||||
Self::clear_interrupt_flags();
|
||||
result
|
||||
}
|
||||
|
||||
fn setup_clocks() {
|
||||
critical_section::with(|_cs| {
|
||||
// RM says the pllsaidivr should only be changed when pllsai is off. But this could have other unintended side effects. So let's just give it a try like this.
|
||||
// According to the debugger, this bit gets set, anyway.
|
||||
#[cfg(stm32f7)]
|
||||
crate::pac::RCC
|
||||
.dckcfgr1()
|
||||
.modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2));
|
||||
|
||||
// It is set to RCC_PLLSAIDIVR_2 in ST's BSP example for the STM32469I-DISCO.
|
||||
#[cfg(stm32f4)]
|
||||
crate::pac::RCC
|
||||
.dckcfgr()
|
||||
.modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2));
|
||||
});
|
||||
|
||||
rcc::enable_and_reset::<T>();
|
||||
}
|
||||
|
||||
fn clear_interrupt_flags() {
|
||||
T::regs().icr().write(|w| {
|
||||
w.set_cfuif(Cfuif::CLEAR);
|
||||
w.set_clif(Clif::CLEAR);
|
||||
w.set_crrif(Crrif::CLEAR);
|
||||
w.set_cterrif(Cterrif::CLEAR);
|
||||
});
|
||||
}
|
||||
|
||||
fn enable_interrupts(enable: bool) {
|
||||
T::regs().ier().write(|w| {
|
||||
w.set_fuie(enable);
|
||||
w.set_lie(false); // we are not interested in the line interrupt enable event
|
||||
w.set_rrie(enable);
|
||||
w.set_terrie(enable)
|
||||
});
|
||||
|
||||
// enable interrupts for LTDC peripheral
|
||||
T::Interrupt::unpend();
|
||||
if enable {
|
||||
unsafe { T::Interrupt::enable() };
|
||||
} else {
|
||||
T::Interrupt::disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> Drop for Ltdc<'d, T> {
|
||||
@ -95,9 +524,12 @@ trait SealedInstance: crate::rcc::SealedRccPeripheral {
|
||||
fn regs() -> crate::pac::ltdc::Ltdc;
|
||||
}
|
||||
|
||||
/// DSI instance trait.
|
||||
/// LTDC instance trait.
|
||||
#[allow(private_bounds)]
|
||||
pub trait Instance: SealedInstance + RccPeripheral + 'static {}
|
||||
pub trait Instance: SealedInstance + Peripheral<P = Self> + crate::rcc::RccPeripheral + 'static + Send {
|
||||
/// Interrupt for this LTDC instance.
|
||||
type Interrupt: interrupt::typelevel::Interrupt;
|
||||
}
|
||||
|
||||
pin_trait!(ClkPin, Instance);
|
||||
pin_trait!(HsyncPin, Instance);
|
||||
@ -128,14 +560,16 @@ pin_trait!(B5Pin, Instance);
|
||||
pin_trait!(B6Pin, Instance);
|
||||
pin_trait!(B7Pin, Instance);
|
||||
|
||||
foreach_peripheral!(
|
||||
(ltdc, $inst:ident) => {
|
||||
impl crate::ltdc::SealedInstance for peripherals::$inst {
|
||||
foreach_interrupt!(
|
||||
($inst:ident, ltdc, LTDC, GLOBAL, $irq:ident) => {
|
||||
impl Instance for peripherals::$inst {
|
||||
type Interrupt = crate::interrupt::typelevel::$irq;
|
||||
}
|
||||
|
||||
impl SealedInstance for peripherals::$inst {
|
||||
fn regs() -> crate::pac::ltdc::Ltdc {
|
||||
crate::pac::$inst
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::ltdc::Instance for peripherals::$inst {}
|
||||
};
|
||||
);
|
||||
|
@ -65,15 +65,19 @@
|
||||
/// Enums defined for peripheral parameters
|
||||
pub mod enums;
|
||||
|
||||
use core::future::poll_fn;
|
||||
use core::marker::PhantomData;
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
pub use enums::*;
|
||||
|
||||
use crate::gpio::{AfType, AnyPin, OutputType, Speed};
|
||||
use crate::pac::tsc::Tsc as Regs;
|
||||
use crate::interrupt::typelevel::Interrupt;
|
||||
use crate::mode::{Async, Blocking, Mode as PeriMode};
|
||||
use crate::rcc::{self, RccPeripheral};
|
||||
use crate::{peripherals, Peripheral};
|
||||
use crate::{interrupt, peripherals, Peripheral};
|
||||
|
||||
#[cfg(tsc_v1)]
|
||||
const TSC_NUM_GROUPS: u32 = 6;
|
||||
@ -90,6 +94,18 @@ pub enum Error {
|
||||
Test,
|
||||
}
|
||||
|
||||
/// TSC interrupt handler.
|
||||
pub struct InterruptHandler<T: Instance> {
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
|
||||
unsafe fn on_interrupt() {
|
||||
T::regs().ier().write(|w| w.set_eoaie(false));
|
||||
T::waker().wake();
|
||||
}
|
||||
}
|
||||
|
||||
/// Pin type definition to control IO parameters
|
||||
pub enum PinType {
|
||||
/// Sensing channel pin connected to an electrode
|
||||
@ -490,7 +506,7 @@ pub enum G7 {}
|
||||
pub enum G8 {}
|
||||
|
||||
/// TSC driver
|
||||
pub struct Tsc<'d, T: Instance> {
|
||||
pub struct Tsc<'d, T: Instance, K: PeriMode> {
|
||||
_peri: PeripheralRef<'d, T>,
|
||||
_g1: Option<PinGroup<'d, T, G1>>,
|
||||
_g2: Option<PinGroup<'d, T, G2>>,
|
||||
@ -504,11 +520,102 @@ pub struct Tsc<'d, T: Instance> {
|
||||
_g8: Option<PinGroup<'d, T, G8>>,
|
||||
state: State,
|
||||
config: Config,
|
||||
_kind: PhantomData<K>,
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> Tsc<'d, T> {
|
||||
/// Create new TSC driver
|
||||
pub fn new(
|
||||
impl<'d, T: Instance> Tsc<'d, T, Async> {
|
||||
/// Create a Tsc instance that can be awaited for completion
|
||||
pub fn new_async(
|
||||
peri: impl Peripheral<P = T> + 'd,
|
||||
g1: Option<PinGroup<'d, T, G1>>,
|
||||
g2: Option<PinGroup<'d, T, G2>>,
|
||||
g3: Option<PinGroup<'d, T, G3>>,
|
||||
g4: Option<PinGroup<'d, T, G4>>,
|
||||
g5: Option<PinGroup<'d, T, G5>>,
|
||||
g6: Option<PinGroup<'d, T, G6>>,
|
||||
#[cfg(any(tsc_v2, tsc_v3))] g7: Option<PinGroup<'d, T, G7>>,
|
||||
#[cfg(tsc_v3)] g8: Option<PinGroup<'d, T, G8>>,
|
||||
config: Config,
|
||||
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
|
||||
) -> Self {
|
||||
// Need to check valid pin configuration input
|
||||
let g1 = g1.filter(|b| b.check_group().is_ok());
|
||||
let g2 = g2.filter(|b| b.check_group().is_ok());
|
||||
let g3 = g3.filter(|b| b.check_group().is_ok());
|
||||
let g4 = g4.filter(|b| b.check_group().is_ok());
|
||||
let g5 = g5.filter(|b| b.check_group().is_ok());
|
||||
let g6 = g6.filter(|b| b.check_group().is_ok());
|
||||
#[cfg(any(tsc_v2, tsc_v3))]
|
||||
let g7 = g7.filter(|b| b.check_group().is_ok());
|
||||
#[cfg(tsc_v3)]
|
||||
let g8 = g8.filter(|b| b.check_group().is_ok());
|
||||
|
||||
match Self::check_shields(
|
||||
&g1,
|
||||
&g2,
|
||||
&g3,
|
||||
&g4,
|
||||
&g5,
|
||||
&g6,
|
||||
#[cfg(any(tsc_v2, tsc_v3))]
|
||||
&g7,
|
||||
#[cfg(tsc_v3)]
|
||||
&g8,
|
||||
) {
|
||||
Ok(()) => Self::new_inner(
|
||||
peri,
|
||||
g1,
|
||||
g2,
|
||||
g3,
|
||||
g4,
|
||||
g5,
|
||||
g6,
|
||||
#[cfg(any(tsc_v2, tsc_v3))]
|
||||
g7,
|
||||
#[cfg(tsc_v3)]
|
||||
g8,
|
||||
config,
|
||||
),
|
||||
Err(_) => Self::new_inner(
|
||||
peri,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
#[cfg(any(tsc_v2, tsc_v3))]
|
||||
None,
|
||||
#[cfg(tsc_v3)]
|
||||
None,
|
||||
config,
|
||||
),
|
||||
}
|
||||
}
|
||||
/// Asyncronously wait for the end of an acquisition
|
||||
pub async fn pend_for_acquisition(&mut self) {
|
||||
poll_fn(|cx| match self.get_state() {
|
||||
State::Busy => {
|
||||
T::waker().register(cx.waker());
|
||||
T::regs().ier().write(|w| w.set_eoaie(true));
|
||||
if self.get_state() != State::Busy {
|
||||
T::regs().ier().write(|w| w.set_eoaie(false));
|
||||
return Poll::Ready(());
|
||||
}
|
||||
Poll::Pending
|
||||
}
|
||||
_ => {
|
||||
T::regs().ier().write(|w| w.set_eoaie(false));
|
||||
Poll::Ready(())
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> Tsc<'d, T, Blocking> {
|
||||
/// Create a Tsc instance that must be polled for completion
|
||||
pub fn new_blocking(
|
||||
peri: impl Peripheral<P = T> + 'd,
|
||||
g1: Option<PinGroup<'d, T, G1>>,
|
||||
g2: Option<PinGroup<'d, T, G2>>,
|
||||
@ -574,7 +681,14 @@ impl<'d, T: Instance> Tsc<'d, T> {
|
||||
),
|
||||
}
|
||||
}
|
||||
/// Wait for end of acquisition
|
||||
pub fn poll_for_acquisition(&mut self) {
|
||||
while self.get_state() == State::Busy {}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, K: PeriMode> Tsc<'d, T, K> {
|
||||
/// Create new TSC driver
|
||||
fn check_shields(
|
||||
g1: &Option<PinGroup<'d, T, G1>>,
|
||||
g2: &Option<PinGroup<'d, T, G2>>,
|
||||
@ -663,7 +777,7 @@ impl<'d, T: Instance> Tsc<'d, T> {
|
||||
|
||||
rcc::enable_and_reset::<T>();
|
||||
|
||||
T::REGS.cr().modify(|w| {
|
||||
T::regs().cr().modify(|w| {
|
||||
w.set_tsce(true);
|
||||
w.set_ctph(config.ct_pulse_high_length.into());
|
||||
w.set_ctpl(config.ct_pulse_low_length.into());
|
||||
@ -691,33 +805,39 @@ impl<'d, T: Instance> Tsc<'d, T> {
|
||||
|
||||
// Set IO configuration
|
||||
// Disable Schmitt trigger hysteresis on all used TSC IOs
|
||||
T::REGS
|
||||
T::regs()
|
||||
.iohcr()
|
||||
.write(|w| w.0 = !(config.channel_ios | config.shield_ios | config.sampling_ios));
|
||||
|
||||
// Set channel and shield IOs
|
||||
T::REGS.ioccr().write(|w| w.0 = config.channel_ios | config.shield_ios);
|
||||
T::regs()
|
||||
.ioccr()
|
||||
.write(|w| w.0 = config.channel_ios | config.shield_ios);
|
||||
|
||||
// Set sampling IOs
|
||||
T::REGS.ioscr().write(|w| w.0 = config.sampling_ios);
|
||||
T::regs().ioscr().write(|w| w.0 = config.sampling_ios);
|
||||
|
||||
// Set the groups to be acquired
|
||||
T::REGS
|
||||
T::regs()
|
||||
.iogcsr()
|
||||
.write(|w| w.0 = Self::extract_groups(config.channel_ios));
|
||||
|
||||
// Disable interrupts
|
||||
T::REGS.ier().modify(|w| {
|
||||
T::regs().ier().modify(|w| {
|
||||
w.set_eoaie(false);
|
||||
w.set_mceie(false);
|
||||
});
|
||||
|
||||
// Clear flags
|
||||
T::REGS.icr().modify(|w| {
|
||||
T::regs().icr().modify(|w| {
|
||||
w.set_eoaic(true);
|
||||
w.set_mceic(true);
|
||||
});
|
||||
|
||||
unsafe {
|
||||
T::Interrupt::enable();
|
||||
}
|
||||
|
||||
Self {
|
||||
_peri: peri,
|
||||
_g1: g1,
|
||||
@ -732,6 +852,7 @@ impl<'d, T: Instance> Tsc<'d, T> {
|
||||
_g8: g8,
|
||||
state: State::Ready,
|
||||
config,
|
||||
_kind: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@ -740,68 +861,41 @@ impl<'d, T: Instance> Tsc<'d, T> {
|
||||
self.state = State::Busy;
|
||||
|
||||
// Disable interrupts
|
||||
T::REGS.ier().modify(|w| {
|
||||
T::regs().ier().modify(|w| {
|
||||
w.set_eoaie(false);
|
||||
w.set_mceie(false);
|
||||
});
|
||||
|
||||
// Clear flags
|
||||
T::REGS.icr().modify(|w| {
|
||||
T::regs().icr().modify(|w| {
|
||||
w.set_eoaic(true);
|
||||
w.set_mceic(true);
|
||||
});
|
||||
|
||||
// Set the touch sensing IOs not acquired to the default mode
|
||||
T::REGS.cr().modify(|w| {
|
||||
T::regs().cr().modify(|w| {
|
||||
w.set_iodef(self.config.io_default_mode);
|
||||
});
|
||||
|
||||
// Start the acquisition
|
||||
T::REGS.cr().modify(|w| {
|
||||
w.set_start(true);
|
||||
});
|
||||
}
|
||||
|
||||
/// Start charge transfer acquisition with interrupts enabled
|
||||
pub fn start_it(&mut self) {
|
||||
self.state = State::Busy;
|
||||
|
||||
// Enable interrupts
|
||||
T::REGS.ier().modify(|w| {
|
||||
w.set_eoaie(true);
|
||||
w.set_mceie(self.config.max_count_interrupt);
|
||||
});
|
||||
|
||||
// Clear flags
|
||||
T::REGS.icr().modify(|w| {
|
||||
w.set_eoaic(true);
|
||||
w.set_mceic(true);
|
||||
});
|
||||
|
||||
// Set the touch sensing IOs not acquired to the default mode
|
||||
T::REGS.cr().modify(|w| {
|
||||
w.set_iodef(self.config.io_default_mode);
|
||||
});
|
||||
|
||||
// Start the acquisition
|
||||
T::REGS.cr().modify(|w| {
|
||||
T::regs().cr().modify(|w| {
|
||||
w.set_start(true);
|
||||
});
|
||||
}
|
||||
|
||||
/// Stop charge transfer acquisition
|
||||
pub fn stop(&mut self) {
|
||||
T::REGS.cr().modify(|w| {
|
||||
T::regs().cr().modify(|w| {
|
||||
w.set_start(false);
|
||||
});
|
||||
|
||||
// Set the touch sensing IOs in low power mode
|
||||
T::REGS.cr().modify(|w| {
|
||||
T::regs().cr().modify(|w| {
|
||||
w.set_iodef(false);
|
||||
});
|
||||
|
||||
// Clear flags
|
||||
T::REGS.icr().modify(|w| {
|
||||
T::regs().icr().modify(|w| {
|
||||
w.set_eoaic(true);
|
||||
w.set_mceic(true);
|
||||
});
|
||||
@ -809,42 +903,11 @@ impl<'d, T: Instance> Tsc<'d, T> {
|
||||
self.state = State::Ready;
|
||||
}
|
||||
|
||||
/// Stop charge transfer acquisition and clear interrupts
|
||||
pub fn stop_it(&mut self) {
|
||||
T::REGS.cr().modify(|w| {
|
||||
w.set_start(false);
|
||||
});
|
||||
|
||||
// Set the touch sensing IOs in low power mode
|
||||
T::REGS.cr().modify(|w| {
|
||||
w.set_iodef(false);
|
||||
});
|
||||
|
||||
// Disable interrupts
|
||||
T::REGS.ier().modify(|w| {
|
||||
w.set_eoaie(false);
|
||||
w.set_mceie(false);
|
||||
});
|
||||
|
||||
// Clear flags
|
||||
T::REGS.icr().modify(|w| {
|
||||
w.set_eoaic(true);
|
||||
w.set_mceic(true);
|
||||
});
|
||||
|
||||
self.state = State::Ready;
|
||||
}
|
||||
|
||||
/// Wait for end of acquisition
|
||||
pub fn poll_for_acquisition(&mut self) {
|
||||
while self.get_state() == State::Busy {}
|
||||
}
|
||||
|
||||
/// Get current state of acquisition
|
||||
pub fn get_state(&mut self) -> State {
|
||||
if self.state == State::Busy {
|
||||
if T::REGS.isr().read().eoaf() {
|
||||
if T::REGS.isr().read().mcef() {
|
||||
if T::regs().isr().read().eoaf() {
|
||||
if T::regs().isr().read().mcef() {
|
||||
self.state = State::Error
|
||||
} else {
|
||||
self.state = State::Ready
|
||||
@ -859,16 +922,16 @@ impl<'d, T: Instance> Tsc<'d, T> {
|
||||
// Status bits are set by hardware when the acquisition on the corresponding
|
||||
// enabled analog IO group is complete, cleared when new acquisition is started
|
||||
let status = match index {
|
||||
Group::One => T::REGS.iogcsr().read().g1s(),
|
||||
Group::Two => T::REGS.iogcsr().read().g2s(),
|
||||
Group::Three => T::REGS.iogcsr().read().g3s(),
|
||||
Group::Four => T::REGS.iogcsr().read().g4s(),
|
||||
Group::Five => T::REGS.iogcsr().read().g5s(),
|
||||
Group::Six => T::REGS.iogcsr().read().g6s(),
|
||||
Group::One => T::regs().iogcsr().read().g1s(),
|
||||
Group::Two => T::regs().iogcsr().read().g2s(),
|
||||
Group::Three => T::regs().iogcsr().read().g3s(),
|
||||
Group::Four => T::regs().iogcsr().read().g4s(),
|
||||
Group::Five => T::regs().iogcsr().read().g5s(),
|
||||
Group::Six => T::regs().iogcsr().read().g6s(),
|
||||
#[cfg(any(tsc_v2, tsc_v3))]
|
||||
Group::Seven => T::REGS.iogcsr().read().g7s(),
|
||||
Group::Seven => T::regs().iogcsr().read().g7s(),
|
||||
#[cfg(tsc_v3)]
|
||||
Group::Eight => T::REGS.iogcsr().read().g8s(),
|
||||
Group::Eight => T::regs().iogcsr().read().g8s(),
|
||||
};
|
||||
match status {
|
||||
true => GroupStatus::Complete,
|
||||
@ -878,39 +941,51 @@ impl<'d, T: Instance> Tsc<'d, T> {
|
||||
|
||||
/// Get the count for the acquisiton, valid once group status is set
|
||||
pub fn group_get_value(&mut self, index: Group) -> u16 {
|
||||
T::REGS.iogcr(index.into()).read().cnt()
|
||||
T::regs().iogcr(index.into()).read().cnt()
|
||||
}
|
||||
|
||||
/// Discharge the IOs for subsequent acquisition
|
||||
pub fn discharge_io(&mut self, status: bool) {
|
||||
// Set the touch sensing IOs in low power mode
|
||||
T::REGS.cr().modify(|w| {
|
||||
T::regs().cr().modify(|w| {
|
||||
w.set_iodef(!status);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> Drop for Tsc<'d, T> {
|
||||
impl<'d, T: Instance, K: PeriMode> Drop for Tsc<'d, T, K> {
|
||||
fn drop(&mut self) {
|
||||
rcc::disable::<T>();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait SealedInstance {
|
||||
const REGS: Regs;
|
||||
fn regs() -> crate::pac::tsc::Tsc;
|
||||
fn waker() -> &'static AtomicWaker;
|
||||
}
|
||||
|
||||
/// TSC instance trait
|
||||
#[allow(private_bounds)]
|
||||
pub trait Instance: Peripheral<P = Self> + SealedInstance + RccPeripheral {}
|
||||
pub trait Instance: Peripheral<P = Self> + SealedInstance + RccPeripheral {
|
||||
/// Interrupt for this TSC instance
|
||||
type Interrupt: interrupt::typelevel::Interrupt;
|
||||
}
|
||||
|
||||
foreach_peripheral!(
|
||||
(tsc, $inst:ident) => {
|
||||
impl SealedInstance for peripherals::$inst {
|
||||
const REGS: Regs = crate::pac::$inst;
|
||||
foreach_interrupt!(
|
||||
($inst:ident, tsc, TSC, GLOBAL, $irq:ident) => {
|
||||
impl Instance for peripherals::$inst {
|
||||
type Interrupt = crate::interrupt::typelevel::$irq;
|
||||
}
|
||||
|
||||
impl Instance for peripherals::$inst {}
|
||||
impl SealedInstance for peripherals::$inst {
|
||||
fn regs() -> crate::pac::tsc::Tsc {
|
||||
crate::pac::$inst
|
||||
}
|
||||
fn waker() -> &'static AtomicWaker {
|
||||
static WAKER: AtomicWaker = AtomicWaker::new();
|
||||
&WAKER
|
||||
}
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
|
@ -371,9 +371,12 @@ impl<'d> UartTx<'d, Async> {
|
||||
pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> {
|
||||
let r = self.info.regs;
|
||||
|
||||
// Disable Receiver for Half-Duplex mode
|
||||
if r.cr3().read().hdsel() {
|
||||
r.cr1().modify(|reg| reg.set_re(false));
|
||||
// Enable Transmitter and disable Receiver for Half-Duplex mode
|
||||
let mut cr1 = r.cr1().read();
|
||||
if r.cr3().read().hdsel() && !cr1.te() {
|
||||
cr1.set_te(true);
|
||||
cr1.set_re(false);
|
||||
r.cr1().write_value(cr1);
|
||||
}
|
||||
|
||||
let ch = self.tx_dma.as_mut().unwrap();
|
||||
@ -474,9 +477,12 @@ impl<'d, M: Mode> UartTx<'d, M> {
|
||||
pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> {
|
||||
let r = self.info.regs;
|
||||
|
||||
// Disable Receiver for Half-Duplex mode
|
||||
if r.cr3().read().hdsel() {
|
||||
r.cr1().modify(|reg| reg.set_re(false));
|
||||
// Enable Transmitter and disable Receiver for Half-Duplex mode
|
||||
let mut cr1 = r.cr1().read();
|
||||
if r.cr3().read().hdsel() && !cr1.te() {
|
||||
cr1.set_te(true);
|
||||
cr1.set_re(false);
|
||||
r.cr1().write_value(cr1);
|
||||
}
|
||||
|
||||
for &b in buffer {
|
||||
@ -561,8 +567,9 @@ impl<'d> UartRx<'d, Async> {
|
||||
) -> Result<ReadCompletionEvent, Error> {
|
||||
let r = self.info.regs;
|
||||
|
||||
// Call flush for Half-Duplex mode. It prevents reading of bytes which have just been written.
|
||||
if r.cr3().read().hdsel() {
|
||||
// Call flush for Half-Duplex mode if some bytes were written and flush was not called.
|
||||
// It prevents reading of bytes which have just been written.
|
||||
if r.cr3().read().hdsel() && r.cr1().read().te() {
|
||||
blocking_flush(self.info)?;
|
||||
}
|
||||
|
||||
@ -898,8 +905,9 @@ impl<'d, M: Mode> UartRx<'d, M> {
|
||||
pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
|
||||
let r = self.info.regs;
|
||||
|
||||
// Call flush for Half-Duplex mode. It prevents reading of bytes which have just been written.
|
||||
if r.cr3().read().hdsel() {
|
||||
// Call flush for Half-Duplex mode if some bytes were written and flush was not called.
|
||||
// It prevents reading of bytes which have just been written.
|
||||
if r.cr3().read().hdsel() && r.cr1().read().te() {
|
||||
blocking_flush(self.info)?;
|
||||
}
|
||||
|
||||
@ -1481,10 +1489,19 @@ fn configure(
|
||||
r.cr1().write(|w| {
|
||||
// enable uart
|
||||
w.set_ue(true);
|
||||
// enable transceiver
|
||||
w.set_te(enable_tx);
|
||||
// enable receiver
|
||||
w.set_re(enable_rx);
|
||||
|
||||
if config.half_duplex {
|
||||
// The te and re bits will be set by write, read and flush methods.
|
||||
// Receiver should be enabled by default for Half-Duplex.
|
||||
w.set_te(false);
|
||||
w.set_re(true);
|
||||
} else {
|
||||
// enable transceiver
|
||||
w.set_te(enable_tx);
|
||||
// enable receiver
|
||||
w.set_re(enable_rx);
|
||||
}
|
||||
|
||||
// configure word size
|
||||
// if using odd or even parity it must be configured to 9bits
|
||||
w.set_m0(if config.parity != Parity::ParityNone {
|
||||
|
@ -29,6 +29,9 @@ reqwless = { version = "0.12.0", features = ["defmt",]}
|
||||
serde = { version = "1.0.203", default-features = false, features = ["derive"] }
|
||||
serde-json-core = "0.5.1"
|
||||
|
||||
# for assign resources example
|
||||
assign-resources = { git = "https://github.com/adamgreig/assign-resources", rev = "94ad10e2729afdf0fd5a77cd12e68409a982f58a" }
|
||||
|
||||
#cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
|
||||
cortex-m = { version = "0.7.6", features = ["inline-asm"] }
|
||||
cortex-m-rt = "0.7.0"
|
||||
|
79
examples/rp/src/bin/assign_resources.rs
Normal file
79
examples/rp/src/bin/assign_resources.rs
Normal file
@ -0,0 +1,79 @@
|
||||
//! This example demonstrates how to assign resources to multiple tasks by splitting up the peripherals.
|
||||
//! It is not about sharing the same resources between tasks, see sharing.rs for that or head to https://embassy.dev/book/#_sharing_peripherals_between_tasks)
|
||||
//! Of course splitting up resources and sharing resources can be combined, yet this example is only about splitting up resources.
|
||||
//!
|
||||
//! There are basically two ways we demonstrate here:
|
||||
//! 1) Assigning resources to a task by passing parts of the peripherals
|
||||
//! 2) Assigning resources to a task by passing a struct with the split up peripherals, using the assign-resources macro
|
||||
//!
|
||||
//! using four LEDs on Pins 10, 11, 20 and 21
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use assign_resources::assign_resources;
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_rp::gpio::{Level, Output};
|
||||
use embassy_rp::peripherals::{self, PIN_20, PIN_21};
|
||||
use embassy_time::Timer;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
// initialize the peripherals
|
||||
let p = embassy_rp::init(Default::default());
|
||||
|
||||
// 1) Assigning a resource to a task by passing parts of the peripherals.
|
||||
spawner
|
||||
.spawn(double_blinky_manually_assigned(spawner, p.PIN_20, p.PIN_21))
|
||||
.unwrap();
|
||||
|
||||
// 2) Using the assign-resources macro to assign resources to a task.
|
||||
// we perform the split, see further below for the definition of the resources struct
|
||||
let r = split_resources!(p);
|
||||
// and then we can use them
|
||||
spawner.spawn(double_blinky_macro_assigned(spawner, r.leds)).unwrap();
|
||||
}
|
||||
|
||||
// 1) Assigning a resource to a task by passing parts of the peripherals.
|
||||
#[embassy_executor::task]
|
||||
async fn double_blinky_manually_assigned(_spawner: Spawner, pin_20: PIN_20, pin_21: PIN_21) {
|
||||
let mut led_20 = Output::new(pin_20, Level::Low);
|
||||
let mut led_21 = Output::new(pin_21, Level::High);
|
||||
|
||||
loop {
|
||||
info!("toggling leds");
|
||||
led_20.toggle();
|
||||
led_21.toggle();
|
||||
Timer::after_secs(1).await;
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Using the assign-resources macro to assign resources to a task.
|
||||
// first we define the resources we want to assign to the task using the assign_resources! macro
|
||||
// basically this will split up the peripherals struct into smaller structs, that we define here
|
||||
// naming is up to you, make sure your future self understands what you did here
|
||||
assign_resources! {
|
||||
leds: Leds{
|
||||
led_10: PIN_10,
|
||||
led_11: PIN_11,
|
||||
}
|
||||
// add more resources to more structs if needed, for example defining one struct for each task
|
||||
}
|
||||
// this could be done in another file and imported here, but for the sake of simplicity we do it here
|
||||
// see https://github.com/adamgreig/assign-resources for more information
|
||||
|
||||
// 2) Using the split resources in a task
|
||||
#[embassy_executor::task]
|
||||
async fn double_blinky_macro_assigned(_spawner: Spawner, r: Leds) {
|
||||
let mut led_10 = Output::new(r.led_10, Level::Low);
|
||||
let mut led_11 = Output::new(r.led_11, Level::High);
|
||||
|
||||
loop {
|
||||
info!("toggling leds");
|
||||
led_10.toggle();
|
||||
led_11.toggle();
|
||||
Timer::after_secs(1).await;
|
||||
}
|
||||
}
|
@ -63,7 +63,8 @@ async fn main(spawner: Spawner) {
|
||||
w5500_int,
|
||||
w5500_reset,
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
.unwrap();
|
||||
unwrap!(spawner.spawn(ethernet_task(runner)));
|
||||
|
||||
// Generate random seed
|
||||
|
@ -66,7 +66,8 @@ async fn main(spawner: Spawner) {
|
||||
w5500_int,
|
||||
w5500_reset,
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
.unwrap();
|
||||
unwrap!(spawner.spawn(ethernet_task(runner)));
|
||||
|
||||
// Generate random seed
|
||||
|
@ -65,7 +65,8 @@ async fn main(spawner: Spawner) {
|
||||
w5500_int,
|
||||
w5500_reset,
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
.unwrap();
|
||||
unwrap!(spawner.spawn(ethernet_task(runner)));
|
||||
|
||||
// Generate random seed
|
||||
|
@ -63,7 +63,8 @@ async fn main(spawner: Spawner) {
|
||||
w5500_int,
|
||||
w5500_reset,
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
.unwrap();
|
||||
unwrap!(spawner.spawn(ethernet_task(runner)));
|
||||
|
||||
// Generate random seed
|
||||
|
150
examples/rp/src/bin/sharing.rs
Normal file
150
examples/rp/src/bin/sharing.rs
Normal file
@ -0,0 +1,150 @@
|
||||
//! This example shows some common strategies for sharing resources between tasks.
|
||||
//!
|
||||
//! We demonstrate five different ways of sharing, covering different use cases:
|
||||
//! - Atomics: This method is used for simple values, such as bool and u8..u32
|
||||
//! - Blocking Mutex: This is used for sharing non-async things, using Cell/RefCell for interior mutability.
|
||||
//! - Async Mutex: This is used for sharing async resources, where you need to hold the lock across await points.
|
||||
//! The async Mutex has interior mutability built-in, so no RefCell is needed.
|
||||
//! - Cell: For sharing Copy types between tasks running on the same executor.
|
||||
//! - RefCell: When you want &mut access to a value shared between tasks running on the same executor.
|
||||
//!
|
||||
//! More information: https://embassy.dev/book/#_sharing_peripherals_between_tasks
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::cell::{Cell, RefCell};
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use cortex_m_rt::entry;
|
||||
use defmt::info;
|
||||
use embassy_executor::{Executor, InterruptExecutor};
|
||||
use embassy_rp::clocks::RoscRng;
|
||||
use embassy_rp::interrupt::{InterruptExt, Priority};
|
||||
use embassy_rp::peripherals::UART0;
|
||||
use embassy_rp::uart::{self, InterruptHandler, UartTx};
|
||||
use embassy_rp::{bind_interrupts, interrupt};
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_sync::{blocking_mutex, mutex};
|
||||
use embassy_time::{Duration, Ticker};
|
||||
use rand::RngCore;
|
||||
use static_cell::{ConstStaticCell, StaticCell};
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
type UartAsyncMutex = mutex::Mutex<CriticalSectionRawMutex, UartTx<'static, UART0, uart::Async>>;
|
||||
|
||||
struct MyType {
|
||||
inner: u32,
|
||||
}
|
||||
|
||||
static EXECUTOR_HI: InterruptExecutor = InterruptExecutor::new();
|
||||
static EXECUTOR_LOW: StaticCell<Executor> = StaticCell::new();
|
||||
|
||||
// Use Atomics for simple values
|
||||
static ATOMIC: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
// Use blocking Mutex with Cell/RefCell for sharing non-async things
|
||||
static MUTEX_BLOCKING: blocking_mutex::Mutex<CriticalSectionRawMutex, RefCell<MyType>> =
|
||||
blocking_mutex::Mutex::new(RefCell::new(MyType { inner: 0 }));
|
||||
|
||||
bind_interrupts!(struct Irqs {
|
||||
UART0_IRQ => InterruptHandler<UART0>;
|
||||
});
|
||||
|
||||
#[interrupt]
|
||||
unsafe fn SWI_IRQ_0() {
|
||||
EXECUTOR_HI.on_interrupt()
|
||||
}
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let p = embassy_rp::init(Default::default());
|
||||
info!("Here we go!");
|
||||
|
||||
let uart = UartTx::new(p.UART0, p.PIN_0, p.DMA_CH0, uart::Config::default());
|
||||
// Use the async Mutex for sharing async things (built-in interior mutability)
|
||||
static UART: StaticCell<UartAsyncMutex> = StaticCell::new();
|
||||
let uart = UART.init(mutex::Mutex::new(uart));
|
||||
|
||||
// High-priority executor: runs in interrupt mode
|
||||
interrupt::SWI_IRQ_0.set_priority(Priority::P3);
|
||||
let spawner = EXECUTOR_HI.start(interrupt::SWI_IRQ_0);
|
||||
spawner.must_spawn(task_a(uart));
|
||||
|
||||
// Low priority executor: runs in thread mode
|
||||
let executor = EXECUTOR_LOW.init(Executor::new());
|
||||
executor.run(|spawner| {
|
||||
// No Mutex needed when sharing between tasks running on the same executor
|
||||
|
||||
// Use Cell for Copy-types
|
||||
static CELL: ConstStaticCell<Cell<[u8; 4]>> = ConstStaticCell::new(Cell::new([0; 4]));
|
||||
let cell = CELL.take();
|
||||
|
||||
// Use RefCell for &mut access
|
||||
static REF_CELL: ConstStaticCell<RefCell<MyType>> = ConstStaticCell::new(RefCell::new(MyType { inner: 0 }));
|
||||
let ref_cell = REF_CELL.take();
|
||||
|
||||
spawner.must_spawn(task_b(uart, cell, ref_cell));
|
||||
spawner.must_spawn(task_c(cell, ref_cell));
|
||||
});
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task_a(uart: &'static UartAsyncMutex) {
|
||||
let mut ticker = Ticker::every(Duration::from_secs(1));
|
||||
loop {
|
||||
let random = RoscRng.next_u32();
|
||||
|
||||
{
|
||||
let mut uart = uart.lock().await;
|
||||
uart.write(b"task a").await.unwrap();
|
||||
// The uart lock is released when it goes out of scope
|
||||
}
|
||||
|
||||
ATOMIC.store(random, Ordering::Relaxed);
|
||||
|
||||
MUTEX_BLOCKING.lock(|x| x.borrow_mut().inner = random);
|
||||
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task_b(uart: &'static UartAsyncMutex, cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell<MyType>) {
|
||||
let mut ticker = Ticker::every(Duration::from_secs(1));
|
||||
loop {
|
||||
let random = RoscRng.next_u32();
|
||||
|
||||
uart.lock().await.write(b"task b").await.unwrap();
|
||||
|
||||
cell.set(random.to_be_bytes());
|
||||
|
||||
ref_cell.borrow_mut().inner = random;
|
||||
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task_c(cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell<MyType>) {
|
||||
let mut ticker = Ticker::every(Duration::from_secs(1));
|
||||
loop {
|
||||
info!("=======================");
|
||||
|
||||
let atomic_val = ATOMIC.load(Ordering::Relaxed);
|
||||
info!("atomic: {}", atomic_val);
|
||||
|
||||
MUTEX_BLOCKING.lock(|x| {
|
||||
let val = x.borrow().inner;
|
||||
info!("blocking mutex: {}", val);
|
||||
});
|
||||
|
||||
let cell_val = cell.get();
|
||||
info!("cell: {:?}", cell_val);
|
||||
|
||||
let ref_cell_val = ref_cell.borrow().inner;
|
||||
info!("ref_cell: {:?}", ref_cell_val);
|
||||
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ async fn main(_spawner: Spawner) {
|
||||
// Startup delay can be combined to the maximum of either
|
||||
delay.delay_us(Temperature::start_time_us().max(VrefInt::start_time_us()));
|
||||
|
||||
let vrefint_sample = adc.read(&mut vrefint);
|
||||
let vrefint_sample = adc.blocking_read(&mut vrefint);
|
||||
|
||||
let convert_to_millivolts = |sample| {
|
||||
// From http://www.st.com/resource/en/datasheet/DM00071990.pdf
|
||||
@ -50,16 +50,16 @@ async fn main(_spawner: Spawner) {
|
||||
|
||||
loop {
|
||||
// Read pin
|
||||
let v = adc.read(&mut pin);
|
||||
let v = adc.blocking_read(&mut pin);
|
||||
info!("PC1: {} ({} mV)", v, convert_to_millivolts(v));
|
||||
|
||||
// Read internal temperature
|
||||
let v = adc.read(&mut temp);
|
||||
let v = adc.blocking_read(&mut temp);
|
||||
let celcius = convert_to_celcius(v);
|
||||
info!("Internal temp: {} ({} C)", v, celcius);
|
||||
|
||||
// Read internal voltage reference
|
||||
let v = adc.read(&mut vrefint);
|
||||
let v = adc.blocking_read(&mut vrefint);
|
||||
info!("VrefInt: {}", v);
|
||||
|
||||
Timer::after_millis(100).await;
|
||||
|
83
examples/stm32f4/src/bin/adc_dma.rs
Normal file
83
examples/stm32f4/src/bin/adc_dma.rs
Normal file
@ -0,0 +1,83 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
use cortex_m::singleton;
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_stm32::adc::{Adc, RingBufferedAdc, SampleTime, Sequence};
|
||||
use embassy_stm32::Peripherals;
|
||||
use embassy_time::Instant;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
spawner.must_spawn(adc_task(p));
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn adc_task(mut p: Peripherals) {
|
||||
const ADC_BUF_SIZE: usize = 1024;
|
||||
let adc_data: &mut [u16; ADC_BUF_SIZE] = singleton!(ADCDAT : [u16; ADC_BUF_SIZE] = [0u16; ADC_BUF_SIZE]).unwrap();
|
||||
let adc_data2: &mut [u16; ADC_BUF_SIZE] = singleton!(ADCDAT2 : [u16; ADC_BUF_SIZE] = [0u16; ADC_BUF_SIZE]).unwrap();
|
||||
|
||||
let adc = Adc::new(p.ADC1);
|
||||
let adc2 = Adc::new(p.ADC2);
|
||||
|
||||
let mut adc: RingBufferedAdc<embassy_stm32::peripherals::ADC1> = adc.into_ring_buffered(p.DMA2_CH0, adc_data);
|
||||
let mut adc2: RingBufferedAdc<embassy_stm32::peripherals::ADC2> = adc2.into_ring_buffered(p.DMA2_CH2, adc_data2);
|
||||
|
||||
adc.set_sample_sequence(Sequence::One, &mut p.PA0, SampleTime::CYCLES112);
|
||||
adc.set_sample_sequence(Sequence::Two, &mut p.PA2, SampleTime::CYCLES112);
|
||||
adc2.set_sample_sequence(Sequence::One, &mut p.PA1, SampleTime::CYCLES112);
|
||||
adc2.set_sample_sequence(Sequence::Two, &mut p.PA3, SampleTime::CYCLES112);
|
||||
|
||||
// Note that overrun is a big consideration in this implementation. Whatever task is running the adc.read() calls absolutely must circle back around
|
||||
// to the adc.read() call before the DMA buffer is wrapped around > 1 time. At this point, the overrun is so significant that the context of
|
||||
// what channel is at what index is lost. The buffer must be cleared and reset. This *is* handled here, but allowing this to happen will cause
|
||||
// a reduction of performance as each time the buffer is reset, the adc & dma buffer must be restarted.
|
||||
|
||||
// An interrupt executor with a higher priority than other tasks may be a good approach here, allowing this task to wake and read the buffer most
|
||||
// frequently.
|
||||
let mut tic = Instant::now();
|
||||
let mut buffer1 = [0u16; 512];
|
||||
let mut buffer2 = [0u16; 512];
|
||||
let _ = adc.start();
|
||||
let _ = adc2.start();
|
||||
loop {
|
||||
match adc.read(&mut buffer1).await {
|
||||
Ok(_data) => {
|
||||
let toc = Instant::now();
|
||||
info!(
|
||||
"\n adc1: {} dt = {}, n = {}",
|
||||
buffer1[0..16],
|
||||
(toc - tic).as_micros(),
|
||||
_data
|
||||
);
|
||||
tic = toc;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Error: {:?}", e);
|
||||
buffer1 = [0u16; 512];
|
||||
let _ = adc.start();
|
||||
}
|
||||
}
|
||||
|
||||
match adc2.read(&mut buffer2).await {
|
||||
Ok(_data) => {
|
||||
let toc = Instant::now();
|
||||
info!(
|
||||
"\n adc2: {} dt = {}, n = {}",
|
||||
buffer2[0..16],
|
||||
(toc - tic).as_micros(),
|
||||
_data
|
||||
);
|
||||
tic = toc;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Error: {:?}", e);
|
||||
buffer2 = [0u16; 512];
|
||||
let _ = adc2.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -80,7 +80,9 @@ async fn main(spawner: Spawner) -> ! {
|
||||
let mac_addr = [0x02, 234, 3, 4, 82, 231];
|
||||
static STATE: StaticCell<State<2, 2>> = StaticCell::new();
|
||||
let state = STATE.init(State::<2, 2>::new());
|
||||
let (device, runner) = embassy_net_wiznet::new(mac_addr, state, spi, w5500_int, w5500_reset).await;
|
||||
let (device, runner) = embassy_net_wiznet::new(mac_addr, state, spi, w5500_int, w5500_reset)
|
||||
.await
|
||||
.unwrap();
|
||||
unwrap!(spawner.spawn(ethernet_task(runner)));
|
||||
|
||||
let config = embassy_net::Config::dhcpv4(Default::default());
|
||||
|
@ -16,7 +16,7 @@ async fn main(_spawner: Spawner) {
|
||||
let mut pin = p.PA3;
|
||||
|
||||
let mut vrefint = adc.enable_vrefint();
|
||||
let vrefint_sample = adc.read(&mut vrefint);
|
||||
let vrefint_sample = adc.blocking_read(&mut vrefint);
|
||||
let convert_to_millivolts = |sample| {
|
||||
// From http://www.st.com/resource/en/datasheet/DM00273119.pdf
|
||||
// 6.3.27 Reference voltage
|
||||
@ -26,7 +26,7 @@ async fn main(_spawner: Spawner) {
|
||||
};
|
||||
|
||||
loop {
|
||||
let v = adc.read(&mut pin);
|
||||
let v = adc.blocking_read(&mut pin);
|
||||
info!("--> {} - {} mV", v, convert_to_millivolts(v));
|
||||
Timer::after_millis(100).await;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ async fn main(_spawner: Spawner) {
|
||||
let mut pin = p.PA1;
|
||||
|
||||
let mut vrefint = adc.enable_vrefint();
|
||||
let vrefint_sample = adc.read(&mut vrefint);
|
||||
let vrefint_sample = adc.blocking_read(&mut vrefint);
|
||||
let convert_to_millivolts = |sample| {
|
||||
// From https://www.st.com/resource/en/datasheet/stm32g031g8.pdf
|
||||
// 6.3.3 Embedded internal reference voltage
|
||||
@ -27,7 +27,7 @@ async fn main(_spawner: Spawner) {
|
||||
};
|
||||
|
||||
loop {
|
||||
let v = adc.read(&mut pin);
|
||||
let v = adc.blocking_read(&mut pin);
|
||||
info!("--> {} - {} mV", v, convert_to_millivolts(v));
|
||||
Timer::after_millis(100).await;
|
||||
}
|
||||
|
44
examples/stm32g0/src/bin/adc_dma.rs
Normal file
44
examples/stm32g0/src/bin/adc_dma.rs
Normal file
@ -0,0 +1,44 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_stm32::adc::{Adc, AdcChannel as _, SampleTime};
|
||||
use embassy_time::Timer;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
static mut DMA_BUF: [u16; 2] = [0; 2];
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) {
|
||||
let mut read_buffer = unsafe { &mut DMA_BUF[..] };
|
||||
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
|
||||
info!("Hello World!");
|
||||
|
||||
let mut adc = Adc::new(p.ADC1);
|
||||
|
||||
let mut dma = p.DMA1_CH1;
|
||||
let mut vrefint_channel = adc.enable_vrefint().degrade_adc();
|
||||
let mut pa0 = p.PA0.degrade_adc();
|
||||
|
||||
loop {
|
||||
adc.read(
|
||||
&mut dma,
|
||||
[
|
||||
(&mut vrefint_channel, SampleTime::CYCLES160_5),
|
||||
(&mut pa0, SampleTime::CYCLES160_5),
|
||||
]
|
||||
.into_iter(),
|
||||
&mut read_buffer,
|
||||
)
|
||||
.await;
|
||||
|
||||
let vrefint = read_buffer[0];
|
||||
let measured = read_buffer[1];
|
||||
info!("vrefint: {}", vrefint);
|
||||
info!("measured: {}", measured);
|
||||
Timer::after_millis(500).await;
|
||||
}
|
||||
}
|
43
examples/stm32g0/src/bin/adc_oversampling.rs
Normal file
43
examples/stm32g0/src/bin/adc_oversampling.rs
Normal file
@ -0,0 +1,43 @@
|
||||
//! adc oversampling example
|
||||
//!
|
||||
//! This example uses adc oversampling to achieve 16bit data
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_stm32::adc::{Adc, SampleTime};
|
||||
use embassy_time::Timer;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
info!("Adc oversample test");
|
||||
|
||||
let mut adc = Adc::new(p.ADC1);
|
||||
adc.set_sample_time(SampleTime::CYCLES1_5);
|
||||
let mut pin = p.PA1;
|
||||
|
||||
// From https://www.st.com/resource/en/reference_manual/rm0444-stm32g0x1-advanced-armbased-32bit-mcus-stmicroelectronics.pdf
|
||||
// page373 15.8 Oversampler
|
||||
// Table 76. Maximum output results vs N and M. Grayed values indicates truncation
|
||||
// 0x00 oversampling ratio X2
|
||||
// 0x01 oversampling ratio X4
|
||||
// 0x02 oversampling ratio X8
|
||||
// 0x03 oversampling ratio X16
|
||||
// 0x04 oversampling ratio X32
|
||||
// 0x05 oversampling ratio X64
|
||||
// 0x06 oversampling ratio X128
|
||||
// 0x07 oversampling ratio X256
|
||||
adc.set_oversampling_ratio(0x03);
|
||||
adc.set_oversampling_shift(0b0000);
|
||||
adc.oversampling_enable(true);
|
||||
|
||||
loop {
|
||||
let v = adc.blocking_read(&mut pin);
|
||||
info!("--> {} ", v); //max 65520 = 0xFFF0
|
||||
Timer::after_millis(100).await;
|
||||
}
|
||||
}
|
@ -32,7 +32,7 @@ async fn main(_spawner: Spawner) {
|
||||
adc.set_sample_time(SampleTime::CYCLES24_5);
|
||||
|
||||
loop {
|
||||
let measured = adc.read(&mut p.PA7);
|
||||
let measured = adc.blocking_read(&mut p.PA7);
|
||||
info!("measured: {}", measured);
|
||||
Timer::after_millis(500).await;
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ stm32-fmc = "0.3.0"
|
||||
embedded-storage = "0.3.1"
|
||||
static_cell = "2"
|
||||
chrono = { version = "^0.4", default-features = false }
|
||||
grounded = "0.2.0"
|
||||
|
||||
# cargo build/run
|
||||
[profile.dev]
|
||||
|
@ -51,9 +51,9 @@ async fn main(_spawner: Spawner) {
|
||||
let mut vrefint_channel = adc.enable_vrefint();
|
||||
|
||||
loop {
|
||||
let vrefint = adc.read(&mut vrefint_channel);
|
||||
let vrefint = adc.blocking_read(&mut vrefint_channel);
|
||||
info!("vrefint: {}", vrefint);
|
||||
let measured = adc.read(&mut p.PC0);
|
||||
let measured = adc.blocking_read(&mut p.PC0);
|
||||
info!("measured: {}", measured);
|
||||
Timer::after_millis(500).await;
|
||||
}
|
||||
|
76
examples/stm32h7/src/bin/adc_dma.rs
Normal file
76
examples/stm32h7/src/bin/adc_dma.rs
Normal file
@ -0,0 +1,76 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_stm32::adc::{Adc, AdcChannel as _, SampleTime};
|
||||
use embassy_stm32::Config;
|
||||
use embassy_time::Timer;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
#[link_section = ".ram_d3"]
|
||||
static mut DMA_BUF: [u16; 2] = [0; 2];
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) {
|
||||
let mut read_buffer = unsafe { &mut DMA_BUF[..] };
|
||||
|
||||
let mut config = Config::default();
|
||||
{
|
||||
use embassy_stm32::rcc::*;
|
||||
config.rcc.hsi = Some(HSIPrescaler::DIV1);
|
||||
config.rcc.csi = true;
|
||||
config.rcc.pll1 = Some(Pll {
|
||||
source: PllSource::HSI,
|
||||
prediv: PllPreDiv::DIV4,
|
||||
mul: PllMul::MUL50,
|
||||
divp: Some(PllDiv::DIV2),
|
||||
divq: Some(PllDiv::DIV8), // SPI1 cksel defaults to pll1_q
|
||||
divr: None,
|
||||
});
|
||||
config.rcc.pll2 = Some(Pll {
|
||||
source: PllSource::HSI,
|
||||
prediv: PllPreDiv::DIV4,
|
||||
mul: PllMul::MUL50,
|
||||
divp: Some(PllDiv::DIV8), // 100mhz
|
||||
divq: None,
|
||||
divr: None,
|
||||
});
|
||||
config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz
|
||||
config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz
|
||||
config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz
|
||||
config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz
|
||||
config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz
|
||||
config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz
|
||||
config.rcc.voltage_scale = VoltageScale::Scale1;
|
||||
config.rcc.mux.adcsel = mux::Adcsel::PLL2_P;
|
||||
}
|
||||
let p = embassy_stm32::init(config);
|
||||
|
||||
info!("Hello World!");
|
||||
|
||||
let mut adc = Adc::new(p.ADC3);
|
||||
|
||||
let mut dma = p.DMA1_CH1;
|
||||
let mut vrefint_channel = adc.enable_vrefint().degrade_adc();
|
||||
let mut pc0 = p.PC0.degrade_adc();
|
||||
|
||||
loop {
|
||||
adc.read(
|
||||
&mut dma,
|
||||
[
|
||||
(&mut vrefint_channel, SampleTime::CYCLES387_5),
|
||||
(&mut pc0, SampleTime::CYCLES810_5),
|
||||
]
|
||||
.into_iter(),
|
||||
&mut read_buffer,
|
||||
)
|
||||
.await;
|
||||
|
||||
let vrefint = read_buffer[0];
|
||||
let measured = read_buffer[1];
|
||||
info!("vrefint: {}", vrefint);
|
||||
info!("measured: {}", measured);
|
||||
Timer::after_millis(500).await;
|
||||
}
|
||||
}
|
186
examples/stm32h7/src/bin/sai.rs
Normal file
186
examples/stm32h7/src/bin/sai.rs
Normal file
@ -0,0 +1,186 @@
|
||||
//! Daisy Seed rev.7(with PCM3060 codec)
|
||||
//! https://electro-smith.com/products/daisy-seed
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use embassy_executor::Spawner;
|
||||
use grounded::uninit::GroundedArrayCell;
|
||||
use hal::rcc::*;
|
||||
use hal::sai::*;
|
||||
use hal::time::Hertz;
|
||||
use {defmt_rtt as _, embassy_stm32 as hal, panic_probe as _};
|
||||
|
||||
const BLOCK_LENGTH: usize = 32; // 32 samples
|
||||
const HALF_DMA_BUFFER_LENGTH: usize = BLOCK_LENGTH * 2; // 2 channels
|
||||
const DMA_BUFFER_LENGTH: usize = HALF_DMA_BUFFER_LENGTH * 2; // 2 half-blocks
|
||||
const SAMPLE_RATE: u32 = 48000;
|
||||
|
||||
//DMA buffer must be in special region. Refer https://embassy.dev/book/#_stm32_bdma_only_working_out_of_some_ram_regions
|
||||
#[link_section = ".sram1_bss"]
|
||||
static mut TX_BUFFER: GroundedArrayCell<u32, DMA_BUFFER_LENGTH> = GroundedArrayCell::uninit();
|
||||
#[link_section = ".sram1_bss"]
|
||||
static mut RX_BUFFER: GroundedArrayCell<u32, DMA_BUFFER_LENGTH> = GroundedArrayCell::uninit();
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) {
|
||||
let mut config = hal::Config::default();
|
||||
config.rcc.pll1 = Some(Pll {
|
||||
source: PllSource::HSE,
|
||||
prediv: PllPreDiv::DIV4,
|
||||
mul: PllMul::MUL200,
|
||||
divp: Some(PllDiv::DIV2),
|
||||
divq: Some(PllDiv::DIV5),
|
||||
divr: Some(PllDiv::DIV2),
|
||||
});
|
||||
config.rcc.pll3 = Some(Pll {
|
||||
source: PllSource::HSE,
|
||||
prediv: PllPreDiv::DIV6,
|
||||
mul: PllMul::MUL295,
|
||||
divp: Some(PllDiv::DIV16),
|
||||
divq: Some(PllDiv::DIV4),
|
||||
divr: Some(PllDiv::DIV32),
|
||||
});
|
||||
config.rcc.sys = Sysclk::PLL1_P;
|
||||
config.rcc.mux.sai1sel = hal::pac::rcc::vals::Saisel::PLL3_P;
|
||||
config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz
|
||||
config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz
|
||||
config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz
|
||||
config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz
|
||||
config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz
|
||||
config.rcc.hse = Some(Hse {
|
||||
freq: Hertz::mhz(16),
|
||||
mode: HseMode::Oscillator,
|
||||
});
|
||||
|
||||
let p = hal::init(config);
|
||||
|
||||
let (sub_block_tx, sub_block_rx) = hal::sai::split_subblocks(p.SAI1);
|
||||
let kernel_clock = hal::rcc::frequency::<hal::peripherals::SAI1>().0;
|
||||
let mclk_div = mclk_div_from_u8((kernel_clock / (SAMPLE_RATE * 256)) as u8);
|
||||
|
||||
let mut tx_config = hal::sai::Config::default();
|
||||
tx_config.mode = Mode::Master;
|
||||
tx_config.tx_rx = TxRx::Transmitter;
|
||||
tx_config.sync_output = true;
|
||||
tx_config.clock_strobe = ClockStrobe::Falling;
|
||||
tx_config.master_clock_divider = mclk_div;
|
||||
tx_config.stereo_mono = StereoMono::Stereo;
|
||||
tx_config.data_size = DataSize::Data24;
|
||||
tx_config.bit_order = BitOrder::MsbFirst;
|
||||
tx_config.frame_sync_polarity = FrameSyncPolarity::ActiveHigh;
|
||||
tx_config.frame_sync_offset = FrameSyncOffset::OnFirstBit;
|
||||
tx_config.frame_length = 64;
|
||||
tx_config.frame_sync_active_level_length = embassy_stm32::sai::word::U7(32);
|
||||
tx_config.fifo_threshold = FifoThreshold::Quarter;
|
||||
|
||||
let mut rx_config = tx_config.clone();
|
||||
rx_config.mode = Mode::Slave;
|
||||
rx_config.tx_rx = TxRx::Receiver;
|
||||
rx_config.sync_input = SyncInput::Internal;
|
||||
rx_config.clock_strobe = ClockStrobe::Rising;
|
||||
rx_config.sync_output = false;
|
||||
|
||||
let tx_buffer: &mut [u32] = unsafe {
|
||||
TX_BUFFER.initialize_all_copied(0);
|
||||
let (ptr, len) = TX_BUFFER.get_ptr_len();
|
||||
core::slice::from_raw_parts_mut(ptr, len)
|
||||
};
|
||||
|
||||
let mut sai_transmitter = Sai::new_asynchronous_with_mclk(
|
||||
sub_block_tx,
|
||||
p.PE5,
|
||||
p.PE6,
|
||||
p.PE4,
|
||||
p.PE2,
|
||||
p.DMA1_CH0,
|
||||
tx_buffer,
|
||||
tx_config,
|
||||
);
|
||||
|
||||
let rx_buffer: &mut [u32] = unsafe {
|
||||
RX_BUFFER.initialize_all_copied(0);
|
||||
let (ptr, len) = RX_BUFFER.get_ptr_len();
|
||||
core::slice::from_raw_parts_mut(ptr, len)
|
||||
};
|
||||
|
||||
let mut sai_receiver = Sai::new_synchronous(sub_block_rx, p.PE3, p.DMA1_CH1, rx_buffer, rx_config);
|
||||
|
||||
sai_receiver.start();
|
||||
sai_transmitter.start();
|
||||
|
||||
let mut buf = [0u32; HALF_DMA_BUFFER_LENGTH];
|
||||
|
||||
loop {
|
||||
sai_receiver.read(&mut buf).await.unwrap();
|
||||
sai_transmitter.write(&buf).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
const fn mclk_div_from_u8(v: u8) -> MasterClockDivider {
|
||||
match v {
|
||||
1 => MasterClockDivider::Div1,
|
||||
2 => MasterClockDivider::Div2,
|
||||
3 => MasterClockDivider::Div3,
|
||||
4 => MasterClockDivider::Div4,
|
||||
5 => MasterClockDivider::Div5,
|
||||
6 => MasterClockDivider::Div6,
|
||||
7 => MasterClockDivider::Div7,
|
||||
8 => MasterClockDivider::Div8,
|
||||
9 => MasterClockDivider::Div9,
|
||||
10 => MasterClockDivider::Div10,
|
||||
11 => MasterClockDivider::Div11,
|
||||
12 => MasterClockDivider::Div12,
|
||||
13 => MasterClockDivider::Div13,
|
||||
14 => MasterClockDivider::Div14,
|
||||
15 => MasterClockDivider::Div15,
|
||||
16 => MasterClockDivider::Div16,
|
||||
17 => MasterClockDivider::Div17,
|
||||
18 => MasterClockDivider::Div18,
|
||||
19 => MasterClockDivider::Div19,
|
||||
20 => MasterClockDivider::Div20,
|
||||
21 => MasterClockDivider::Div21,
|
||||
22 => MasterClockDivider::Div22,
|
||||
23 => MasterClockDivider::Div23,
|
||||
24 => MasterClockDivider::Div24,
|
||||
25 => MasterClockDivider::Div25,
|
||||
26 => MasterClockDivider::Div26,
|
||||
27 => MasterClockDivider::Div27,
|
||||
28 => MasterClockDivider::Div28,
|
||||
29 => MasterClockDivider::Div29,
|
||||
30 => MasterClockDivider::Div30,
|
||||
31 => MasterClockDivider::Div31,
|
||||
32 => MasterClockDivider::Div32,
|
||||
33 => MasterClockDivider::Div33,
|
||||
34 => MasterClockDivider::Div34,
|
||||
35 => MasterClockDivider::Div35,
|
||||
36 => MasterClockDivider::Div36,
|
||||
37 => MasterClockDivider::Div37,
|
||||
38 => MasterClockDivider::Div38,
|
||||
39 => MasterClockDivider::Div39,
|
||||
40 => MasterClockDivider::Div40,
|
||||
41 => MasterClockDivider::Div41,
|
||||
42 => MasterClockDivider::Div42,
|
||||
43 => MasterClockDivider::Div43,
|
||||
44 => MasterClockDivider::Div44,
|
||||
45 => MasterClockDivider::Div45,
|
||||
46 => MasterClockDivider::Div46,
|
||||
47 => MasterClockDivider::Div47,
|
||||
48 => MasterClockDivider::Div48,
|
||||
49 => MasterClockDivider::Div49,
|
||||
50 => MasterClockDivider::Div50,
|
||||
51 => MasterClockDivider::Div51,
|
||||
52 => MasterClockDivider::Div52,
|
||||
53 => MasterClockDivider::Div53,
|
||||
54 => MasterClockDivider::Div54,
|
||||
55 => MasterClockDivider::Div55,
|
||||
56 => MasterClockDivider::Div56,
|
||||
57 => MasterClockDivider::Div57,
|
||||
58 => MasterClockDivider::Div58,
|
||||
59 => MasterClockDivider::Div59,
|
||||
60 => MasterClockDivider::Div60,
|
||||
61 => MasterClockDivider::Div61,
|
||||
62 => MasterClockDivider::Div62,
|
||||
63 => MasterClockDivider::Div63,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
@ -10,18 +10,24 @@ use embassy_executor::Executor;
|
||||
use embassy_stm32::mode::Async;
|
||||
use embassy_stm32::time::mhz;
|
||||
use embassy_stm32::{spi, Config};
|
||||
use grounded::uninit::GroundedArrayCell;
|
||||
use heapless::String;
|
||||
use static_cell::StaticCell;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
// Defined in memory.x
|
||||
#[link_section = ".ram_d3"]
|
||||
static mut RAM_D3: [u8; 64 * 1024] = [0u8; 64 * 1024];
|
||||
static mut RAM_D3: GroundedArrayCell<u8, 256> = GroundedArrayCell::uninit();
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn main_task(mut spi: spi::Spi<'static, Async>) {
|
||||
let read_buffer = unsafe { &mut RAM_D3[0..128] };
|
||||
let write_buffer = unsafe { &mut RAM_D3[128..256] };
|
||||
let (read_buffer, write_buffer) = unsafe {
|
||||
RAM_D3.initialize_all_copied(0);
|
||||
(
|
||||
RAM_D3.get_subslice_mut_unchecked(0, 128),
|
||||
RAM_D3.get_subslice_mut_unchecked(128, 128),
|
||||
)
|
||||
};
|
||||
|
||||
for n in 0u32.. {
|
||||
let mut write: String<128> = String::new();
|
||||
|
8
examples/stm32h735/.cargo/config.toml
Normal file
8
examples/stm32h735/.cargo/config.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[target.thumbv7em-none-eabihf]
|
||||
runner = 'probe-rs run --chip STM32H735IGKx'
|
||||
|
||||
[build]
|
||||
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
|
||||
|
||||
[env]
|
||||
DEFMT_LOG = "trace"
|
61
examples/stm32h735/Cargo.toml
Normal file
61
examples/stm32h735/Cargo.toml
Normal file
@ -0,0 +1,61 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "embassy-stm32h735-examples"
|
||||
version = "0.1.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h735ig", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] }
|
||||
embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] }
|
||||
embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" }
|
||||
embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] }
|
||||
embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
|
||||
embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
|
||||
|
||||
defmt = "0.3"
|
||||
defmt-rtt = "0.4"
|
||||
|
||||
cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
|
||||
cortex-m-rt = "0.7.0"
|
||||
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
||||
heapless = { version = "0.8", default-features = false }
|
||||
embedded-graphics = { version = "0.8.1" }
|
||||
tinybmp = { version = "0.5" }
|
||||
|
||||
# cargo build/run
|
||||
[profile.dev]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = true # <-
|
||||
incremental = false
|
||||
opt-level = 3 # <-
|
||||
overflow-checks = true # <-
|
||||
|
||||
# cargo test
|
||||
[profile.test]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = true # <-
|
||||
incremental = false
|
||||
opt-level = 3 # <-
|
||||
overflow-checks = true # <-
|
||||
|
||||
# cargo build/run --release
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = false # <-
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 3 # <-
|
||||
overflow-checks = false # <-
|
||||
|
||||
# cargo test --release
|
||||
[profile.bench]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = false # <-
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 3 # <-
|
||||
overflow-checks = false # <-
|
35
examples/stm32h735/build.rs
Normal file
35
examples/stm32h735/build.rs
Normal file
@ -0,0 +1,35 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
// By default, Cargo will re-run a build script whenever
|
||||
// any file in the project changes. By specifying `memory.x`
|
||||
// here, we ensure the build script is only re-run when
|
||||
// `memory.x` is changed.
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
|
||||
println!("cargo:rustc-link-arg-bins=--nmagic");
|
||||
println!("cargo:rustc-link-arg-bins=-Tlink.x");
|
||||
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
|
||||
}
|
5
examples/stm32h735/memory.x
Normal file
5
examples/stm32h735/memory.x
Normal file
@ -0,0 +1,5 @@
|
||||
MEMORY
|
||||
{
|
||||
FLASH : ORIGIN = 0x08000000, LENGTH = 1024K
|
||||
RAM : ORIGIN = 0x24000000, LENGTH = 320K
|
||||
}
|
BIN
examples/stm32h735/src/bin/ferris.bmp
Normal file
BIN
examples/stm32h735/src/bin/ferris.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
467
examples/stm32h735/src/bin/ltdc.rs
Normal file
467
examples/stm32h735/src/bin/ltdc.rs
Normal file
@ -0,0 +1,467 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![macro_use]
|
||||
#![allow(static_mut_refs)]
|
||||
|
||||
/// This example demonstrates the LTDC lcd display peripheral and was tested to run on an stm32h735g-dk (embassy-stm32 feature "stm32h735ig" and probe-rs chip "STM32H735IGKx")
|
||||
/// Even though the dev kit has 16MB of attached PSRAM this example uses the 320KB of internal AXIS RAM found on the mcu itself to make the example more standalone and portable.
|
||||
/// For this reason a 256 color lookup table had to be used to keep the memory requirement down to an acceptable level.
|
||||
/// The example bounces a ferris crab bitmap around the screen while blinking an led on another task
|
||||
///
|
||||
use bouncy_box::BouncyBox;
|
||||
use defmt::{info, unwrap};
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_stm32::gpio::{Level, Output, Speed};
|
||||
use embassy_stm32::ltdc::{self, Ltdc, LtdcConfiguration, LtdcLayer, LtdcLayerConfig, PolarityActive, PolarityEdge};
|
||||
use embassy_stm32::{bind_interrupts, peripherals};
|
||||
use embassy_time::{Duration, Timer};
|
||||
use embedded_graphics::draw_target::DrawTarget;
|
||||
use embedded_graphics::geometry::{OriginDimensions, Point, Size};
|
||||
use embedded_graphics::image::Image;
|
||||
use embedded_graphics::pixelcolor::raw::RawU24;
|
||||
use embedded_graphics::pixelcolor::Rgb888;
|
||||
use embedded_graphics::prelude::*;
|
||||
use embedded_graphics::primitives::Rectangle;
|
||||
use embedded_graphics::Pixel;
|
||||
use heapless::{Entry, FnvIndexMap};
|
||||
use tinybmp::Bmp;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
const DISPLAY_WIDTH: usize = 480;
|
||||
const DISPLAY_HEIGHT: usize = 272;
|
||||
const MY_TASK_POOL_SIZE: usize = 2;
|
||||
|
||||
// the following two display buffers consume 261120 bytes that just about fits into axis ram found on the mcu
|
||||
pub static mut FB1: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT];
|
||||
pub static mut FB2: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT];
|
||||
|
||||
bind_interrupts!(struct Irqs {
|
||||
LTDC => ltdc::InterruptHandler<peripherals::LTDC>;
|
||||
});
|
||||
|
||||
const NUM_COLORS: usize = 256;
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
let p = rcc_setup::stm32h735g_init();
|
||||
|
||||
// blink the led on another task
|
||||
let led = Output::new(p.PC3, Level::High, Speed::Low);
|
||||
unwrap!(spawner.spawn(led_task(led)));
|
||||
|
||||
// numbers from STMicroelectronics/STM32CubeH7 STM32H735G-DK C-based example
|
||||
const RK043FN48H_HSYNC: u16 = 41; // Horizontal synchronization
|
||||
const RK043FN48H_HBP: u16 = 13; // Horizontal back porch
|
||||
const RK043FN48H_HFP: u16 = 32; // Horizontal front porch
|
||||
const RK043FN48H_VSYNC: u16 = 10; // Vertical synchronization
|
||||
const RK043FN48H_VBP: u16 = 2; // Vertical back porch
|
||||
const RK043FN48H_VFP: u16 = 2; // Vertical front porch
|
||||
|
||||
let ltdc_config = LtdcConfiguration {
|
||||
active_width: DISPLAY_WIDTH as _,
|
||||
active_height: DISPLAY_HEIGHT as _,
|
||||
h_back_porch: RK043FN48H_HBP - 11, // -11 from MX_LTDC_Init
|
||||
h_front_porch: RK043FN48H_HFP,
|
||||
v_back_porch: RK043FN48H_VBP,
|
||||
v_front_porch: RK043FN48H_VFP,
|
||||
h_sync: RK043FN48H_HSYNC,
|
||||
v_sync: RK043FN48H_VSYNC,
|
||||
h_sync_polarity: PolarityActive::ActiveLow,
|
||||
v_sync_polarity: PolarityActive::ActiveLow,
|
||||
data_enable_polarity: PolarityActive::ActiveHigh,
|
||||
pixel_clock_polarity: PolarityEdge::FallingEdge,
|
||||
};
|
||||
|
||||
info!("init ltdc");
|
||||
let mut ltdc = Ltdc::new_with_pins(
|
||||
p.LTDC, Irqs, p.PG7, p.PC6, p.PA4, p.PG14, p.PD0, p.PD6, p.PA8, p.PE12, p.PA3, p.PB8, p.PB9, p.PB1, p.PB0,
|
||||
p.PA6, p.PE11, p.PH15, p.PH4, p.PC7, p.PD3, p.PE0, p.PH3, p.PH8, p.PH9, p.PH10, p.PH11, p.PE1, p.PE15,
|
||||
);
|
||||
ltdc.init(<dc_config);
|
||||
|
||||
// we only need to draw on one layer for this example (not to be confused with the double buffer)
|
||||
info!("enable bottom layer");
|
||||
let layer_config = LtdcLayerConfig {
|
||||
pixel_format: ltdc::PixelFormat::L8, // 1 byte per pixel
|
||||
layer: LtdcLayer::Layer1,
|
||||
window_x0: 0,
|
||||
window_x1: DISPLAY_WIDTH as _,
|
||||
window_y0: 0,
|
||||
window_y1: DISPLAY_HEIGHT as _,
|
||||
};
|
||||
|
||||
let ferris_bmp: Bmp<Rgb888> = Bmp::from_slice(include_bytes!("./ferris.bmp")).unwrap();
|
||||
let color_map = build_color_lookup_map(&ferris_bmp);
|
||||
let clut = build_clut(&color_map);
|
||||
|
||||
// enable the bottom layer with a 256 color lookup table
|
||||
ltdc.init_layer(&layer_config, Some(&clut));
|
||||
|
||||
// Safety: the DoubleBuffer controls access to the statically allocated frame buffers
|
||||
// and it is the only thing that mutates their content
|
||||
let mut double_buffer = DoubleBuffer::new(
|
||||
unsafe { FB1.as_mut() },
|
||||
unsafe { FB2.as_mut() },
|
||||
layer_config,
|
||||
color_map,
|
||||
);
|
||||
|
||||
// this allows us to perform some simple animation for every frame
|
||||
let mut bouncy_box = BouncyBox::new(
|
||||
ferris_bmp.bounding_box(),
|
||||
Rectangle::new(Point::zero(), Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)),
|
||||
2,
|
||||
);
|
||||
|
||||
loop {
|
||||
// cpu intensive drawing to the buffer that is NOT currently being copied to the LCD screen
|
||||
double_buffer.clear();
|
||||
let position = bouncy_box.next_point();
|
||||
let ferris = Image::new(&ferris_bmp, position);
|
||||
unwrap!(ferris.draw(&mut double_buffer));
|
||||
|
||||
// perform async dma data transfer to the lcd screen
|
||||
unwrap!(double_buffer.swap(&mut ltdc).await);
|
||||
}
|
||||
}
|
||||
|
||||
/// builds the color look-up table from all unique colors found in the bitmap. This should be a 256 color indexed bitmap to work.
|
||||
fn build_color_lookup_map(bmp: &Bmp<Rgb888>) -> FnvIndexMap<u32, u8, NUM_COLORS> {
|
||||
let mut color_map: FnvIndexMap<u32, u8, NUM_COLORS> = heapless::FnvIndexMap::new();
|
||||
let mut counter: u8 = 0;
|
||||
|
||||
// add black to position 0
|
||||
color_map.insert(Rgb888::new(0, 0, 0).into_storage(), counter).unwrap();
|
||||
counter += 1;
|
||||
|
||||
for Pixel(_point, color) in bmp.pixels() {
|
||||
let raw = color.into_storage();
|
||||
if let Entry::Vacant(v) = color_map.entry(raw) {
|
||||
v.insert(counter).expect("more than 256 colors detected");
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
color_map
|
||||
}
|
||||
|
||||
/// builds the color look-up table from the color map provided
|
||||
fn build_clut(color_map: &FnvIndexMap<u32, u8, NUM_COLORS>) -> [ltdc::RgbColor; NUM_COLORS] {
|
||||
let mut clut = [ltdc::RgbColor::default(); NUM_COLORS];
|
||||
for (color, index) in color_map.iter() {
|
||||
let color = Rgb888::from(RawU24::new(*color));
|
||||
clut[*index as usize] = ltdc::RgbColor {
|
||||
red: color.r(),
|
||||
green: color.g(),
|
||||
blue: color.b(),
|
||||
};
|
||||
}
|
||||
|
||||
clut
|
||||
}
|
||||
|
||||
#[embassy_executor::task(pool_size = MY_TASK_POOL_SIZE)]
|
||||
async fn led_task(mut led: Output<'static>) {
|
||||
let mut counter = 0;
|
||||
loop {
|
||||
info!("blink: {}", counter);
|
||||
counter += 1;
|
||||
|
||||
// on
|
||||
led.set_low();
|
||||
Timer::after(Duration::from_millis(50)).await;
|
||||
|
||||
// off
|
||||
led.set_high();
|
||||
Timer::after(Duration::from_millis(450)).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub type TargetPixelType = u8;
|
||||
|
||||
// A simple double buffer
|
||||
pub struct DoubleBuffer {
|
||||
buf0: &'static mut [TargetPixelType],
|
||||
buf1: &'static mut [TargetPixelType],
|
||||
is_buf0: bool,
|
||||
layer_config: LtdcLayerConfig,
|
||||
color_map: FnvIndexMap<u32, u8, NUM_COLORS>,
|
||||
}
|
||||
|
||||
impl DoubleBuffer {
|
||||
pub fn new(
|
||||
buf0: &'static mut [TargetPixelType],
|
||||
buf1: &'static mut [TargetPixelType],
|
||||
layer_config: LtdcLayerConfig,
|
||||
color_map: FnvIndexMap<u32, u8, NUM_COLORS>,
|
||||
) -> Self {
|
||||
Self {
|
||||
buf0,
|
||||
buf1,
|
||||
is_buf0: true,
|
||||
layer_config,
|
||||
color_map,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current(&mut self) -> (&FnvIndexMap<u32, u8, NUM_COLORS>, &mut [TargetPixelType]) {
|
||||
if self.is_buf0 {
|
||||
(&self.color_map, self.buf0)
|
||||
} else {
|
||||
(&self.color_map, self.buf1)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn swap<T: ltdc::Instance>(&mut self, ltdc: &mut Ltdc<'_, T>) -> Result<(), ltdc::Error> {
|
||||
let (_, buf) = self.current();
|
||||
let frame_buffer = buf.as_ptr();
|
||||
self.is_buf0 = !self.is_buf0;
|
||||
ltdc.set_buffer(self.layer_config.layer, frame_buffer as *const _).await
|
||||
}
|
||||
|
||||
/// Clears the buffer
|
||||
pub fn clear(&mut self) {
|
||||
let (color_map, buf) = self.current();
|
||||
let black = Rgb888::new(0, 0, 0).into_storage();
|
||||
let color_index = color_map.get(&black).expect("no black found in the color map");
|
||||
|
||||
for a in buf.iter_mut() {
|
||||
*a = *color_index; // solid black
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implement DrawTarget for
|
||||
impl DrawTarget for DoubleBuffer {
|
||||
type Color = Rgb888;
|
||||
type Error = ();
|
||||
|
||||
/// Draw a pixel
|
||||
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||
where
|
||||
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||
{
|
||||
let size = self.size();
|
||||
let width = size.width as i32;
|
||||
let height = size.height as i32;
|
||||
let (color_map, buf) = self.current();
|
||||
|
||||
for pixel in pixels {
|
||||
let Pixel(point, color) = pixel;
|
||||
|
||||
if point.x >= 0 && point.y >= 0 && point.x < width && point.y < height {
|
||||
let index = point.y * width + point.x;
|
||||
let raw_color = color.into_storage();
|
||||
|
||||
match color_map.get(&raw_color) {
|
||||
Some(x) => {
|
||||
buf[index as usize] = *x;
|
||||
}
|
||||
None => panic!("color not found in color map: {}", raw_color),
|
||||
};
|
||||
} else {
|
||||
// Ignore invalid points
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for DoubleBuffer {
|
||||
/// Return the size of the display
|
||||
fn size(&self) -> Size {
|
||||
Size::new(
|
||||
(self.layer_config.window_x1 - self.layer_config.window_x0) as _,
|
||||
(self.layer_config.window_y1 - self.layer_config.window_y0) as _,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
mod rcc_setup {
|
||||
|
||||
use embassy_stm32::rcc::{Hse, HseMode, *};
|
||||
use embassy_stm32::time::Hertz;
|
||||
use embassy_stm32::{Config, Peripherals};
|
||||
|
||||
/// Sets up clocks for the stm32h735g mcu
|
||||
/// change this if you plan to use a different microcontroller
|
||||
pub fn stm32h735g_init() -> Peripherals {
|
||||
/*
|
||||
https://github.com/STMicroelectronics/STM32CubeH7/blob/master/Projects/STM32H735G-DK/Examples/GPIO/GPIO_EXTI/Src/main.c
|
||||
@brief System Clock Configuration
|
||||
The system Clock is configured as follow :
|
||||
System Clock source = PLL (HSE)
|
||||
SYSCLK(Hz) = 520000000 (CPU Clock)
|
||||
HCLK(Hz) = 260000000 (AXI and AHBs Clock)
|
||||
AHB Prescaler = 2
|
||||
D1 APB3 Prescaler = 2 (APB3 Clock 130MHz)
|
||||
D2 APB1 Prescaler = 2 (APB1 Clock 130MHz)
|
||||
D2 APB2 Prescaler = 2 (APB2 Clock 130MHz)
|
||||
D3 APB4 Prescaler = 2 (APB4 Clock 130MHz)
|
||||
HSE Frequency(Hz) = 25000000
|
||||
PLL_M = 5
|
||||
PLL_N = 104
|
||||
PLL_P = 1
|
||||
PLL_Q = 4
|
||||
PLL_R = 2
|
||||
VDD(V) = 3.3
|
||||
Flash Latency(WS) = 3
|
||||
*/
|
||||
|
||||
// setup power and clocks for an stm32h735g-dk run from an external 25 Mhz external oscillator
|
||||
let mut config = Config::default();
|
||||
config.rcc.hse = Some(Hse {
|
||||
freq: Hertz::mhz(25),
|
||||
mode: HseMode::Oscillator,
|
||||
});
|
||||
config.rcc.hsi = None;
|
||||
config.rcc.csi = false;
|
||||
config.rcc.pll1 = Some(Pll {
|
||||
source: PllSource::HSE,
|
||||
prediv: PllPreDiv::DIV5, // PLL_M
|
||||
mul: PllMul::MUL104, // PLL_N
|
||||
divp: Some(PllDiv::DIV1),
|
||||
divq: Some(PllDiv::DIV4),
|
||||
divr: Some(PllDiv::DIV2),
|
||||
});
|
||||
// numbers adapted from Drivers/BSP/STM32H735G-DK/stm32h735g_discovery_ospi.c
|
||||
// MX_OSPI_ClockConfig
|
||||
config.rcc.pll2 = Some(Pll {
|
||||
source: PllSource::HSE,
|
||||
prediv: PllPreDiv::DIV5, // PLL_M
|
||||
mul: PllMul::MUL80, // PLL_N
|
||||
divp: Some(PllDiv::DIV5),
|
||||
divq: Some(PllDiv::DIV2),
|
||||
divr: Some(PllDiv::DIV2),
|
||||
});
|
||||
// numbers adapted from Drivers/BSP/STM32H735G-DK/stm32h735g_discovery_lcd.c
|
||||
// MX_LTDC_ClockConfig
|
||||
config.rcc.pll3 = Some(Pll {
|
||||
source: PllSource::HSE,
|
||||
prediv: PllPreDiv::DIV5, // PLL_M
|
||||
mul: PllMul::MUL160, // PLL_N
|
||||
divp: Some(PllDiv::DIV2),
|
||||
divq: Some(PllDiv::DIV2),
|
||||
divr: Some(PllDiv::DIV83),
|
||||
});
|
||||
config.rcc.voltage_scale = VoltageScale::Scale0;
|
||||
config.rcc.supply_config = SupplyConfig::DirectSMPS;
|
||||
config.rcc.sys = Sysclk::PLL1_P;
|
||||
config.rcc.ahb_pre = AHBPrescaler::DIV2;
|
||||
config.rcc.apb1_pre = APBPrescaler::DIV2;
|
||||
config.rcc.apb2_pre = APBPrescaler::DIV2;
|
||||
config.rcc.apb3_pre = APBPrescaler::DIV2;
|
||||
config.rcc.apb4_pre = APBPrescaler::DIV2;
|
||||
embassy_stm32::init(config)
|
||||
}
|
||||
}
|
||||
|
||||
mod bouncy_box {
|
||||
use embedded_graphics::geometry::Point;
|
||||
use embedded_graphics::primitives::Rectangle;
|
||||
|
||||
enum Direction {
|
||||
DownLeft,
|
||||
DownRight,
|
||||
UpLeft,
|
||||
UpRight,
|
||||
}
|
||||
|
||||
pub struct BouncyBox {
|
||||
direction: Direction,
|
||||
child_rect: Rectangle,
|
||||
parent_rect: Rectangle,
|
||||
current_point: Point,
|
||||
move_by: usize,
|
||||
}
|
||||
|
||||
// This calculates the coordinates of a chile rectangle bounced around inside a parent bounded box
|
||||
impl BouncyBox {
|
||||
pub fn new(child_rect: Rectangle, parent_rect: Rectangle, move_by: usize) -> Self {
|
||||
let center_box = parent_rect.center();
|
||||
let center_img = child_rect.center();
|
||||
let current_point = Point::new(center_box.x - center_img.x / 2, center_box.y - center_img.y / 2);
|
||||
Self {
|
||||
direction: Direction::DownRight,
|
||||
child_rect,
|
||||
parent_rect,
|
||||
current_point,
|
||||
move_by,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_point(&mut self) -> Point {
|
||||
let direction = &self.direction;
|
||||
let img_height = self.child_rect.size.height as i32;
|
||||
let box_height = self.parent_rect.size.height as i32;
|
||||
let img_width = self.child_rect.size.width as i32;
|
||||
let box_width = self.parent_rect.size.width as i32;
|
||||
let move_by = self.move_by as i32;
|
||||
|
||||
match direction {
|
||||
Direction::DownLeft => {
|
||||
self.current_point.x -= move_by;
|
||||
self.current_point.y += move_by;
|
||||
|
||||
let x_out_of_bounds = self.current_point.x < 0;
|
||||
let y_out_of_bounds = (self.current_point.y + img_height) > box_height;
|
||||
|
||||
if x_out_of_bounds && y_out_of_bounds {
|
||||
self.direction = Direction::UpRight
|
||||
} else if x_out_of_bounds && !y_out_of_bounds {
|
||||
self.direction = Direction::DownRight
|
||||
} else if !x_out_of_bounds && y_out_of_bounds {
|
||||
self.direction = Direction::UpLeft
|
||||
}
|
||||
}
|
||||
Direction::DownRight => {
|
||||
self.current_point.x += move_by;
|
||||
self.current_point.y += move_by;
|
||||
|
||||
let x_out_of_bounds = (self.current_point.x + img_width) > box_width;
|
||||
let y_out_of_bounds = (self.current_point.y + img_height) > box_height;
|
||||
|
||||
if x_out_of_bounds && y_out_of_bounds {
|
||||
self.direction = Direction::UpLeft
|
||||
} else if x_out_of_bounds && !y_out_of_bounds {
|
||||
self.direction = Direction::DownLeft
|
||||
} else if !x_out_of_bounds && y_out_of_bounds {
|
||||
self.direction = Direction::UpRight
|
||||
}
|
||||
}
|
||||
Direction::UpLeft => {
|
||||
self.current_point.x -= move_by;
|
||||
self.current_point.y -= move_by;
|
||||
|
||||
let x_out_of_bounds = self.current_point.x < 0;
|
||||
let y_out_of_bounds = self.current_point.y < 0;
|
||||
|
||||
if x_out_of_bounds && y_out_of_bounds {
|
||||
self.direction = Direction::DownRight
|
||||
} else if x_out_of_bounds && !y_out_of_bounds {
|
||||
self.direction = Direction::UpRight
|
||||
} else if !x_out_of_bounds && y_out_of_bounds {
|
||||
self.direction = Direction::DownLeft
|
||||
}
|
||||
}
|
||||
Direction::UpRight => {
|
||||
self.current_point.x += move_by;
|
||||
self.current_point.y -= move_by;
|
||||
|
||||
let x_out_of_bounds = (self.current_point.x + img_width) > box_width;
|
||||
let y_out_of_bounds = self.current_point.y < 0;
|
||||
|
||||
if x_out_of_bounds && y_out_of_bounds {
|
||||
self.direction = Direction::DownLeft
|
||||
} else if x_out_of_bounds && !y_out_of_bounds {
|
||||
self.direction = Direction::UpLeft
|
||||
} else if !x_out_of_bounds && y_out_of_bounds {
|
||||
self.direction = Direction::DownRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.current_point
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
# Change stm32l072cz to your chip name, if necessary.
|
||||
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "time-driver-any", "exti", "memory-x"] }
|
||||
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "unstable-pac", "time-driver-any", "exti", "memory-x"] }
|
||||
embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] }
|
||||
embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
|
||||
embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
|
||||
|
116
examples/stm32l0/src/bin/dds.rs
Normal file
116
examples/stm32l0/src/bin/dds.rs
Normal file
@ -0,0 +1,116 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::option::Option::Some;
|
||||
|
||||
use defmt::info;
|
||||
use defmt_rtt as _; // global logger
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_stm32::gpio::OutputType;
|
||||
use embassy_stm32::rcc::*;
|
||||
use embassy_stm32::time::hz;
|
||||
use embassy_stm32::timer::low_level::{Timer as LLTimer, *};
|
||||
use embassy_stm32::timer::simple_pwm::PwmPin;
|
||||
use embassy_stm32::timer::Channel;
|
||||
use embassy_stm32::{interrupt, pac, Config};
|
||||
use panic_probe as _;
|
||||
|
||||
const DDS_SINE_DATA: [u8; 256] = [
|
||||
0x80, 0x83, 0x86, 0x89, 0x8c, 0x8f, 0x92, 0x95, 0x98, 0x9c, 0x9f, 0xa2, 0xa5, 0xa8, 0xab, 0xae, 0xb0, 0xb3, 0xb6,
|
||||
0xb9, 0xbc, 0xbf, 0xc1, 0xc4, 0xc7, 0xc9, 0xcc, 0xce, 0xd1, 0xd3, 0xd5, 0xd8, 0xda, 0xdc, 0xde, 0xe0, 0xe2, 0xe4,
|
||||
0xe6, 0xe8, 0xea, 0xec, 0xed, 0xef, 0xf0, 0xf2, 0xf3, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfc, 0xfd,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfc, 0xfc, 0xfb,
|
||||
0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf3, 0xf2, 0xf0, 0xef, 0xed, 0xec, 0xea, 0xe8, 0xe6, 0xe4, 0xe2, 0xe0, 0xde,
|
||||
0xdc, 0xda, 0xd8, 0xd5, 0xd3, 0xd1, 0xce, 0xcc, 0xc9, 0xc7, 0xc4, 0xc1, 0xbf, 0xbc, 0xb9, 0xb6, 0xb3, 0xb0, 0xae,
|
||||
0xab, 0xa8, 0xa5, 0xa2, 0x9f, 0x9c, 0x98, 0x95, 0x92, 0x8f, 0x8c, 0x89, 0x86, 0x83, 0x80, 0x7c, 0x79, 0x76, 0x73,
|
||||
0x70, 0x6d, 0x6a, 0x67, 0x63, 0x60, 0x5d, 0x5a, 0x57, 0x54, 0x51, 0x4f, 0x4c, 0x49, 0x46, 0x43, 0x40, 0x3e, 0x3b,
|
||||
0x38, 0x36, 0x33, 0x31, 0x2e, 0x2c, 0x2a, 0x27, 0x25, 0x23, 0x21, 0x1f, 0x1d, 0x1b, 0x19, 0x17, 0x15, 0x13, 0x12,
|
||||
0x10, 0x0f, 0x0d, 0x0c, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
0x0a, 0x0c, 0x0d, 0x0f, 0x10, 0x12, 0x13, 0x15, 0x17, 0x19, 0x1b, 0x1d, 0x1f, 0x21, 0x23, 0x25, 0x27, 0x2a, 0x2c,
|
||||
0x2e, 0x31, 0x33, 0x36, 0x38, 0x3b, 0x3e, 0x40, 0x43, 0x46, 0x49, 0x4c, 0x4f, 0x51, 0x54, 0x57, 0x5a, 0x5d, 0x60,
|
||||
0x63, 0x67, 0x6a, 0x6d, 0x70, 0x73, 0x76, 0x79, 0x7c,
|
||||
];
|
||||
|
||||
// frequency: 15625/(256/(DDS_INCR/2**24)) = 999,99999Hz
|
||||
static mut DDS_INCR: u32 = 0x10624DD2;
|
||||
|
||||
// fractional phase accumulator
|
||||
static mut DDS_AKKU: u32 = 0x00000000;
|
||||
|
||||
#[interrupt]
|
||||
fn TIM2() {
|
||||
unsafe {
|
||||
// get next value of DDS
|
||||
DDS_AKKU = DDS_AKKU.wrapping_add(DDS_INCR);
|
||||
let value = (DDS_SINE_DATA[(DDS_AKKU >> 24) as usize] as u16) << 3;
|
||||
|
||||
// set new output compare value
|
||||
pac::TIM2.ccr(2).modify(|w| w.set_ccr(value));
|
||||
|
||||
// reset interrupt flag
|
||||
pac::TIM2.sr().modify(|r| r.set_uif(false));
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) {
|
||||
info!("Hello World!");
|
||||
|
||||
// configure for 32MHz (HSI16 * 6 / 3)
|
||||
let mut config = Config::default();
|
||||
config.rcc.sys = Sysclk::PLL1_R;
|
||||
config.rcc.hsi = true;
|
||||
config.rcc.pll = Some(Pll {
|
||||
source: PllSource::HSI,
|
||||
div: PllDiv::DIV3,
|
||||
mul: PllMul::MUL6,
|
||||
});
|
||||
|
||||
let p = embassy_stm32::init(config);
|
||||
|
||||
// setup PWM pin in AF mode
|
||||
let _ch3 = PwmPin::new_ch3(p.PA2, OutputType::PushPull);
|
||||
|
||||
// initialize timer
|
||||
// we cannot use SimplePWM here because the Time is privately encapsulated
|
||||
let timer = LLTimer::new(p.TIM2);
|
||||
|
||||
// set counting mode
|
||||
timer.set_counting_mode(CountingMode::EdgeAlignedUp);
|
||||
|
||||
// set pwm sample frequency
|
||||
timer.set_frequency(hz(15625));
|
||||
|
||||
// enable outputs
|
||||
timer.enable_outputs();
|
||||
|
||||
// start timer
|
||||
timer.start();
|
||||
|
||||
// set output compare mode
|
||||
timer.set_output_compare_mode(Channel::Ch3, OutputCompareMode::PwmMode1);
|
||||
|
||||
// set output compare preload
|
||||
timer.set_output_compare_preload(Channel::Ch3, true);
|
||||
|
||||
// set output polarity
|
||||
timer.set_output_polarity(Channel::Ch3, OutputPolarity::ActiveHigh);
|
||||
|
||||
// set compare value
|
||||
timer.set_compare_value(Channel::Ch3, timer.get_max_compare_value() / 2);
|
||||
|
||||
// enable pwm channel
|
||||
timer.enable_channel(Channel::Ch3, true);
|
||||
|
||||
// enable timer interrupts
|
||||
timer.enable_update_interrupt(true);
|
||||
unsafe { cortex_m::peripheral::NVIC::unmask(interrupt::TIM2) };
|
||||
|
||||
async {
|
||||
loop {
|
||||
embassy_time::Timer::after_millis(5000).await;
|
||||
}
|
||||
}
|
||||
.await;
|
||||
}
|
@ -23,7 +23,7 @@ fn main() -> ! {
|
||||
let mut channel = p.PC0;
|
||||
|
||||
loop {
|
||||
let v = adc.read(&mut channel);
|
||||
let v = adc.blocking_read(&mut channel);
|
||||
info!("--> {}", v);
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ fn main() -> ! {
|
||||
let mut channel = p.PC0;
|
||||
|
||||
loop {
|
||||
let v = adc.read(&mut channel);
|
||||
let v = adc.blocking_read(&mut channel);
|
||||
info!("--> {}", v);
|
||||
embassy_time::block_for(Duration::from_millis(200));
|
||||
}
|
||||
|
@ -2,10 +2,15 @@
|
||||
#![no_main]
|
||||
|
||||
use defmt::*;
|
||||
use embassy_stm32::bind_interrupts;
|
||||
use embassy_stm32::tsc::{self, *};
|
||||
use embassy_time::Timer;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
bind_interrupts!(struct Irqs {
|
||||
TSC => InterruptHandler<embassy_stm32::peripherals::TSC>;
|
||||
});
|
||||
|
||||
#[cortex_m_rt::exception]
|
||||
unsafe fn HardFault(_: &cortex_m_rt::ExceptionFrame) -> ! {
|
||||
cortex_m::peripheral::SCB::sys_reset();
|
||||
@ -45,7 +50,7 @@ async fn main(_spawner: embassy_executor::Spawner) {
|
||||
g7.set_io2(context.PE3, PinType::Sample);
|
||||
g7.set_io3(context.PE4, PinType::Channel);
|
||||
|
||||
let mut touch_controller = tsc::Tsc::new(
|
||||
let mut touch_controller = tsc::Tsc::new_async(
|
||||
context.TSC,
|
||||
Some(g1),
|
||||
Some(g2),
|
||||
@ -56,6 +61,7 @@ async fn main(_spawner: embassy_executor::Spawner) {
|
||||
Some(g7),
|
||||
None,
|
||||
config,
|
||||
Irqs,
|
||||
);
|
||||
|
||||
touch_controller.discharge_io(true);
|
||||
@ -67,7 +73,7 @@ async fn main(_spawner: embassy_executor::Spawner) {
|
||||
let mut group_seven_val = 0;
|
||||
info!("Starting touch_controller interface");
|
||||
loop {
|
||||
touch_controller.poll_for_acquisition();
|
||||
touch_controller.pend_for_acquisition().await;
|
||||
touch_controller.discharge_io(true);
|
||||
Timer::after_millis(1).await;
|
||||
|
||||
|
@ -59,7 +59,8 @@ async fn main(spawner: Spawner) {
|
||||
w5500_int,
|
||||
w5500_reset,
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
.unwrap();
|
||||
unwrap!(spawner.spawn(ethernet_task(runner)));
|
||||
|
||||
// Generate random seed
|
||||
|
@ -38,7 +38,7 @@ async fn main(_spawner: Spawner) {
|
||||
dac.set(Value::Bit8(0));
|
||||
// Now wait a little to obtain a stable value
|
||||
Timer::after_millis(30).await;
|
||||
let offset = adc.read(&mut adc_pin);
|
||||
let offset = adc.blocking_read(&mut adc_pin);
|
||||
|
||||
for v in 0..=255 {
|
||||
// First set the DAC output value
|
||||
@ -49,7 +49,7 @@ async fn main(_spawner: Spawner) {
|
||||
Timer::after_millis(30).await;
|
||||
|
||||
// Need to steal the peripherals here because PA4 is obviously in use already
|
||||
let measured = adc.read(&mut unsafe { embassy_stm32::Peripherals::steal() }.PA4);
|
||||
let measured = adc.blocking_read(&mut unsafe { embassy_stm32::Peripherals::steal() }.PA4);
|
||||
// Calibrate and normalize the measurement to get close to the dac_output_val
|
||||
let measured_normalized = ((measured as i32 - offset as i32) / normalization_factor) as i16;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user