Merge branch 'main' into nrf9151

This commit is contained in:
nerwalt 2024-07-11 06:58:05 -06:00
commit 38d8abef26
60 changed files with 3138 additions and 248 deletions

1
ci.sh
View File

@ -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 \

View File

@ -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]

View File

@ -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].

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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,
},
)
))
}

View File

@ -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()))
}
}
}

View File

@ -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

View File

@ -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> {

View File

@ -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();

View File

@ -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

View File

@ -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"]

View File

@ -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
@ -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)),

View File

@ -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())

View File

@ -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)]

View 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>();
}
}

View File

@ -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

View File

@ -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)
}
// Enable ADC
/// 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();
}
}
// 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);
});
T::regs().cr().modify(|reg| {
reg.set_aden(true);
reg.set_ovr(true);
});
while !T::regs().isr().read().adrdy() {
// spin
#[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_adstart(true);
});
// 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() {}
}
}
}

View File

@ -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() {}
}
}
}

View File

@ -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;

View File

@ -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 {

View File

@ -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,
});
rcc::enable_and_reset::<T>();
//new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh));
// 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);
w.set_vspol(match config.v_sync_polarity {
PolarityActive::ActiveHigh => Vspol::ACTIVEHIGH,
PolarityActive::ActiveLow => Vspol::ACTIVELOW,
});
*/
Self { _peri: PhantomData }
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,
});
});
// set synchronization pulse width
ltdc.sscr().modify(|w| {
w.set_vsh(config.v_sync - 1);
w.set_hsw(config.h_sync - 1);
});
// 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 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 {}
};
);

View File

@ -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 {}
foreach_peripheral!(
(tsc, $inst:ident) => {
impl SealedInstance for peripherals::$inst {
const REGS: Regs = crate::pac::$inst;
pub trait Instance: Peripheral<P = Self> + SealedInstance + RccPeripheral {
/// Interrupt for this TSC instance
type Interrupt: interrupt::typelevel::Interrupt;
}
impl Instance for peripherals::$inst {}
foreach_interrupt!(
($inst:ident, tsc, TSC, GLOBAL, $irq:ident) => {
impl Instance for peripherals::$inst {
type Interrupt = crate::interrupt::typelevel::$irq;
}
impl SealedInstance for peripherals::$inst {
fn regs() -> crate::pac::tsc::Tsc {
crate::pac::$inst
}
fn waker() -> &'static AtomicWaker {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
}
};
);

View File

@ -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);
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 {

View File

@ -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"

View 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;
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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;
}
}

View File

@ -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;

View 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();
}
}
}
}

View File

@ -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());

View File

@ -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;
}

View File

@ -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;
}

View 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;
}
}

View 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;
}
}

View File

@ -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;
}

View File

@ -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]

View File

@ -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;
}

View 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;
}
}

View 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!(),
}
}

View File

@ -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();

View 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"

View 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 # <-

View 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");
}

View File

@ -0,0 +1,5 @@
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 1024K
RAM : ORIGIN = 0x24000000, LENGTH = 320K
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View 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(&ltdc_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
}
}
}

View File

@ -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"] }

View 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;
}

View File

@ -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);
}
}

View File

@ -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));
}

View File

@ -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;

View File

@ -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

View File

@ -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;