Skip to content

Commit

Permalink
UART v2 (atsamd-rs#443)
Browse files Browse the repository at this point in the history
Provide a new UART driver with an API similar to `sercom::v2::spi`

Co-authored-by: Ian Rees <code@ianrees.nz>
  • Loading branch information
jbeaurivage and ianrrees committed Jul 31, 2021
1 parent 9eee523 commit 62f9dfc
Show file tree
Hide file tree
Showing 43 changed files with 4,332 additions and 756 deletions.
4 changes: 4 additions & 0 deletions boards/feather_m0/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,7 @@ required-features = ["adalogger", "usb", "sdmmc", "unproven"]
[[example]]
name = "blinky_rtic"
required-features = ["rtic", "unproven"]

[[example]]
name = "uart"
required-features = ["dma"]
100 changes: 100 additions & 0 deletions boards/feather_m0/examples/uart.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//! This example showcases the uart::v2 module.

#![no_std]
#![no_main]

use cortex_m::asm;
use panic_halt as _;

use bsp::hal;
use bsp::pac;
use feather_m0 as bsp;

use bsp::entry;
use hal::clock::GenericClockController;
use hal::dmac::{DmaController, PriorityLevel};
use hal::prelude::*;

use pac::Peripherals;

#[entry]
fn main() -> ! {
let mut peripherals = Peripherals::take().unwrap();
let mut clocks = GenericClockController::with_internal_32kosc(
peripherals.GCLK,
&mut peripherals.PM,
&mut peripherals.SYSCTRL,
&mut peripherals.NVMCTRL,
);

let mut pm = peripherals.PM;
let dmac = peripherals.DMAC;
let pins = bsp::Pins::new(peripherals.PORT);

// Take RX and TX pins
let (rx_pin, tx_pin) = (pins.d0, pins.d1);

// Setup DMA channels for later use
let mut dmac = DmaController::init(dmac, &mut pm);
let channels = dmac.split();

let chan0 = channels.0.init(PriorityLevel::LVL0);
let chan1 = channels.1.init(PriorityLevel::LVL0);

// Setup UART peripheral
let uart = bsp::uart(
&mut clocks,
9600.hz(),
peripherals.SERCOM0,
&mut pm,
rx_pin,
tx_pin,
);

// Split uart in rx + tx halves
let (mut rx, mut tx) = uart.split();

// Get a 50 byte buffer to store data to send/receive
const LENGTH: usize = 50;
let rx_buffer: &'static mut [u8; LENGTH] =
cortex_m::singleton!(: [u8; LENGTH] = [0x00; LENGTH]).unwrap();
let tx_buffer: &'static mut [u8; LENGTH] =
cortex_m::singleton!(: [u8; LENGTH] = [0x00; LENGTH]).unwrap();

// For fun, store numbers from 0 to 49 in buffer
for (i, c) in tx_buffer.iter_mut().enumerate() {
*c = i as u8;
}

// Send data in a blocking way
for c in tx_buffer.iter() {
nb::block!(tx.write(*c)).unwrap();
}

// We'll now receive data in a blocking way
rx.flush_rx_buffer();
for c in rx_buffer.iter_mut() {
*c = nb::block!(rx.read()).unwrap();
}

// Finally, we'll receive AND send data at the same time with DMA

// Setup a DMA transfer to send our data asynchronously.
// We'll set the waker to be a no-op
let tx_dma = tx.send_with_dma(tx_buffer, chan0, |_| {});

// Setup a DMA transfer to receive our data asynchronously.
// Again, we'll set the waker to be a no-op
let rx_dma = rx.receive_with_dma(rx_buffer, chan1, |_| {});

// Wait for transmit DMA transfer to complete
let (_chan0, _tx_buffer, _tx) = tx_dma.wait();

// Wait for receive DMA transfer to complete
let (_chan1, _rx_buffer, _rx) = rx_dma.wait();

loop {
// Go to sleep
asm::wfi();
}
}
20 changes: 15 additions & 5 deletions boards/feather_m0/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ pub use embedded_hal as ehal;
pub use hal::pac;

use hal::clock::GenericClockController;
use hal::sercom::v2::{spi, Sercom4};
use hal::sercom::{I2CMaster3, UART0};
use hal::sercom::{
v2::{
spi,
uart::{self, BaudMode, Oversampling},
Sercom0, Sercom4,
},
I2CMaster3,
};
use hal::time::Hertz;

#[cfg(feature = "usb")]
Expand Down Expand Up @@ -260,8 +266,10 @@ pub fn i2c_master(
I2CMaster3::new(clock, baud, sercom3, pm, sda, scl)
}

pub type UartPads = uart::Pads<Sercom0, UartRx, UartTx>;

/// UART device for the labelled RX & TX pins
pub type Uart = UART0<UartRx, UartTx, (), ()>;
pub type Uart = uart::Uart<uart::Config<UartPads>, uart::Duplex>;

/// Convenience for setting up the labelled RX, TX pins to
/// operate as a UART device running at the specified baud.
Expand All @@ -276,8 +284,10 @@ pub fn uart(
let gclk0 = clocks.gclk0();
let clock = &clocks.sercom0_core(&gclk0).unwrap();
let baud = baud.into();
let pads = (uart_rx.into(), uart_tx.into());
UART0::new(clock, baud, sercom0, pm, pads)
let pads = uart::Pads::default().rx(uart_rx.into()).tx(uart_tx.into());
uart::Config::new(pm, sercom0, pads, clock.freq())
.baud(baud, BaudMode::Fractional(Oversampling::Bits16))
.enable()
}

#[cfg(feature = "usb")]
Expand Down
4 changes: 4 additions & 0 deletions boards/feather_m4/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,7 @@ name = "sleeping_timer_rtc"
[[example]]
name = "dmac"
required-features = ["dma"]

[[example]]
name = "uart"
required-features = ["dma"]
101 changes: 101 additions & 0 deletions boards/feather_m4/examples/uart.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//! This example showcases the uart::v2 module.

#![no_std]
#![no_main]

use cortex_m::asm;
use panic_halt as _;

use bsp::hal;
use bsp::pac;
use feather_m4 as bsp;

use bsp::entry;
use hal::clock::GenericClockController;
use hal::dmac::{DmaController, PriorityLevel};
use hal::prelude::*;

use pac::Peripherals;

#[entry]
fn main() -> ! {
let mut peripherals = Peripherals::take().unwrap();
let mut clocks = GenericClockController::with_internal_32kosc(
peripherals.GCLK,
&mut peripherals.MCLK,
&mut peripherals.OSC32KCTRL,
&mut peripherals.OSCCTRL,
&mut peripherals.NVMCTRL,
);

let mut mclk = peripherals.MCLK;
let dmac = peripherals.DMAC;
let pins = bsp::Pins::new(peripherals.PORT);

// Take RX and TX pins
let (rx_pin, tx_pin) = (pins.d0, pins.d1);

// Setup DMA channels for later use
let mut dmac = DmaController::init(dmac, &mut peripherals.PM);
let channels = dmac.split();

let chan0 = channels.0.init(PriorityLevel::LVL0);
let chan1 = channels.1.init(PriorityLevel::LVL0);

// Setup UART peripheral
let uart = bsp::uart(
&mut clocks,
9600.hz(),
peripherals.SERCOM5,
&mut mclk,
rx_pin,
tx_pin,
);

// Split uart in rx + tx halves
let (mut rx, mut tx) = uart.split();

// Get a 50 byte buffer to store data to send/receive
const LENGTH: usize = 50;
let rx_buffer: &'static mut [u8; LENGTH] =
cortex_m::singleton!(: [u8; LENGTH] = [0x00; LENGTH]).unwrap();
let tx_buffer: &'static mut [u8; LENGTH] =
cortex_m::singleton!(: [u8; LENGTH] = [0x00; LENGTH]).unwrap();

// For fun, store numbers from 0 to 49 in buffer
for (i, c) in tx_buffer.iter_mut().enumerate() {
*c = i as u8;
}

// Send data in a blocking way
for c in tx_buffer.iter() {
nb::block!(tx.write(*c)).unwrap();
}

// We'll now receive data in a blocking way
rx.flush_rx_buffer();
for c in rx_buffer.iter_mut() {
*c = nb::block!(rx.read()).unwrap();
}

// Finally, we'll receive AND send data at the same time with DMA

// Setup a DMA transfer to send our data asynchronously.
// We'll set the waker to be a no-op
let tx_dma = tx.send_with_dma(tx_buffer, chan0, |_| {});

// Setup a DMA transfer to receive our data asynchronously.
// Again, we'll set the waker to be a no-op
let rx_dma = rx.receive_with_dma(rx_buffer, chan1, |_| {});

// Wait for transmit DMA transfer to complete
let (_chan0, _tx_buffer, _tx) = tx_dma.wait();

// Wait for receive DMA transfer to complete
let (_chan1, _rx_buffer, _rx) = rx_dma.wait();

loop {
// Go to sleep
asm::wfi();
}
}
58 changes: 34 additions & 24 deletions boards/feather_m4/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#![no_std]
#![recursion_limit = "1024"]

extern crate atsamd_hal as hal;
pub use atsamd_hal as hal;

#[cfg(feature = "rt")]
extern crate cortex_m_rt;
use cortex_m_rt;
#[cfg(feature = "rt")]
pub use cortex_m_rt::entry;

Expand All @@ -15,13 +15,21 @@ pub use hal::common::*;
pub use hal::samd51::*;
pub use hal::target_device as pac;

use gpio::{Floating, Input, PfC, Port};
use gpio::{Floating, Input, Port};
use hal::clock::GenericClockController;
use hal::sercom::{I2CMaster2, PadPin, SPIMaster1, UART5};
use hal::sercom::{
v2::{
uart::{self, BaudMode, Oversampling},
IoSet1, Sercom5,
},
I2CMaster2, PadPin, SPIMaster1,
};
use hal::time::Hertz;

use gpio::v2::{AlternateC, AnyPin, Pin, C, PB16, PB17};

#[cfg(feature = "usb")]
use gpio::v2::{AnyPin, PA24, PA25};
use gpio::v2::{PA24, PA25};
#[cfg(feature = "usb")]
use hal::usb::usb_device::bus::UsbBusAllocator;
#[cfg(feature = "usb")]
Expand Down Expand Up @@ -148,31 +156,33 @@ pub fn i2c_master<F: Into<Hertz>>(
)
}

pub type UartRx = Pin<PB17, AlternateC>;
pub type UartTx = Pin<PB16, AlternateC>;
pub type UartPads = uart::Pads<Sercom5, IoSet1, UartRx, UartTx>;

/// UART device for the labelled RX & TX pins
pub type Uart = uart::Uart<uart::Config<UartPads>, uart::Duplex>;

/// Convenience for setting up the labelled RX, TX pins to
/// operate as a UART device running at the specified baud.
pub fn uart<F: Into<Hertz>>(
pub fn uart(
clocks: &mut GenericClockController,
baud: F,
sercom5: pac::SERCOM5,
baud: impl Into<Hertz>,
sercom5: Sercom5,
mclk: &mut pac::MCLK,
d0: gpio::Pb17<Input<Floating>>,
d1: gpio::Pb16<Input<Floating>>,
port: &mut Port,
) -> UART5<
hal::sercom::Sercom5Pad1<gpio::Pb17<PfC>>,
hal::sercom::Sercom5Pad0<gpio::Pb16<PfC>>,
(),
(),
> {
rx: impl AnyPin<Id = PB17>,
tx: impl AnyPin<Id = PB16>,
) -> Uart {
let gclk0 = clocks.gclk0();

UART5::new(
&clocks.sercom5_core(&gclk0).unwrap(),
baud.into(),
sercom5,
mclk,
(d0.into_pad(port), d1.into_pad(port)),
)
let clock = &clocks.sercom5_core(&gclk0).unwrap();
let baud = baud.into();
let pads = uart::Pads::default()
.rx(rx.into().into_alternate::<C>())
.tx(tx.into().into_alternate::<C>());
uart::Config::new(mclk, sercom5, pads, clock.freq())
.baud(baud, BaudMode::Fractional(Oversampling::Bits16))
.enable()
}

#[cfg(feature = "usb")]
Expand Down
1 change: 1 addition & 0 deletions hal/src/dmac/channel/reg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ reg_proxy!(swtrigctrl, bit, rw);
/// Acts as a proxy to the PAC DMAC object. Only registers and bits
/// within registers that should be readable/writable by specific
/// [`Channel`]s are exposed.
#[allow(dead_code)]
pub(super) struct RegisterBlock<Id: ChId> {
pub chctrla: ChctrlaProxy<Id, CHCTRLA>,
pub chctrlb: ChctrlbProxy<Id, CHCTRLB>,
Expand Down
7 changes: 4 additions & 3 deletions hal/src/sercom/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@
//! The [v2] module will eventually replace [v1]. New users are encouraged to
//! use [v2] instead of [v1].
//!
//! The new [`v2::spi`] module is substantially more configurable and safe than
//! the existing, [`v1::spi`] module. To assist in migration, the
//! [`v2::spi::Pads`] struct accepts both [`v1::Pin`]s and [`v2::Pin`]s.
//! The new [`v2::spi`] and [`v2::uart`] modules are substantially more
//! configurable and safe than the existing, [`v1::spi`] and [`v1::uart`]
//! modules. To assist in migration, the [`v2::spi::Pads`] and
//! [`v2::uart::Pads`] structs accept both [`v1::Pin`]s and [`v2::Pin`]s.
//!
//! [`Pad`]: v2::pads::Pad
//! [`v1::Pin`]: crate::gpio::v1::Pin
Expand Down
4 changes: 4 additions & 0 deletions hal/src/sercom/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ pub mod pad;
pub use pad::*;

pub mod spi_future;
pub mod uart;

#[cfg(feature = "dma")]
pub mod dma;

//==============================================================================
// Sercom
Expand Down

0 comments on commit 62f9dfc

Please sign in to comment.