diff --git a/imxrt-hal/README.md b/imxrt-hal/README.md index 7eb42111..802a47b8 100644 --- a/imxrt-hal/README.md +++ b/imxrt-hal/README.md @@ -1,18 +1,22 @@ -# imxrt1060-hal +# imxrt-hal -`imxrt1060-hal` is a Rust hardware abstraction layer that's specific to i.MX -RT 1060 processors. This includes some of the following processor parts: +This project provides a Rust HAL (hardware abstraction layer) for all NXP i.MX RT +microcontrollers based on the imxrt-ral crate. -- i.MX RT 1061 -- i.MX RT 1062 +A feature flag needs to be set for any of the supported i.MX RT SoC. -It is the successor to `imxrt-hal`, version 0.4, with `feature = "imxrt1062"`. +## What is it? -## Features +imxrt-hal is an experiment into a lightweight hardware abstraction layer. It +provides access to some of the peripherals of the i.MX RT series processors +from NXP using embedded-hal and other community driven hardware APIs. -The table below describes the optional features supported by `imxrt1060-hal`. +The main aims are fast compilation, compactness, and simplicity. -| Feature | Description | -| -------- | ---------------------------------- | -| `"rt"` | Runtime support with `cortex-m-rt` | -| `"rtic"` | Support for RTIC | +Please consider trying it out and contributing or leaving feedback! + +## Goals + +* Simple to use and hard to use incorrectly +* All peripherals and busses supported +* Support the entire i.MX RT Series with a single crate to maximize code reuse diff --git a/imxrt-hal/src/can/embedded_hal.rs b/imxrt-hal/src/can/embedded_hal.rs index c90db430..4d6a918c 100644 --- a/imxrt-hal/src/can/embedded_hal.rs +++ b/imxrt-hal/src/can/embedded_hal.rs @@ -1,6 +1,6 @@ //! `embedded_hal` trait impls. -use super::{Data, ExtendedId, Frame, Id, OverrunError, StandardId, CAN}; +use super::{Data, ExtendedId, Frame, Id, NoDataError, StandardId, CAN}; use crate::iomuxc::consts::Unsigned; use embedded_hal::can; @@ -11,7 +11,7 @@ where { type Frame = Frame; - type Error = OverrunError; + type Error = NoDataError; fn transmit(&mut self, frame: &Self::Frame) -> nb::Result, Self::Error> { match self.transmit(frame) { @@ -22,14 +22,14 @@ where } fn receive(&mut self) -> nb::Result { - let data: [u8; 8] = [255, 255, 255, 255, 255, 255, 255, 255]; - let id = StandardId::new(0).unwrap(); - - Ok(Frame::new_data(id, Data::new(&data).unwrap())) + match self.read_mailboxes() { + Some(d) => Ok(d.frame), + None => Err(nb::Error::Other(NoDataError { _priv: () })), + } } } -impl can::Error for OverrunError { +impl can::Error for NoDataError { fn kind(&self) -> can::ErrorKind { can::ErrorKind::Overrun } diff --git a/imxrt-hal/src/can/mod.rs b/imxrt-hal/src/can/mod.rs index e5bdcb88..a812945d 100644 --- a/imxrt-hal/src/can/mod.rs +++ b/imxrt-hal/src/can/mod.rs @@ -15,9 +15,10 @@ use crate::ral; use core::convert::Infallible; use core::marker::PhantomData; -/// Error that indicates that an incoming message has been lost due to buffer overrun. +/// Error that indicates that no received frames are available +/// in the FlexCAN mailboxes #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct OverrunError { +pub struct NoDataError { _priv: (), } @@ -911,7 +912,7 @@ where return; } - pub fn read_mailboxes(&mut self) -> Option<()> { + pub fn read_mailboxes(&mut self) -> Option { let mut iflag: u64; let mut cycle_limit: u8 = 3; let offset = self.mailbox_offset(); @@ -946,7 +947,7 @@ where } match self.read_mailbox(self._mailbox_reader_index) { Some(mailbox_data) => { - log::info!("RX Data: {:?}", &mailbox_data,); + return Some(mailbox_data); } _ => {} } diff --git a/imxrt-hal/src/ccm.rs b/imxrt-hal/src/ccm.rs index dfc2be3d..55573ae7 100644 --- a/imxrt-hal/src/ccm.rs +++ b/imxrt-hal/src/ccm.rs @@ -57,6 +57,10 @@ impl PLL1 { PLL1(()) } + #[cfg(any(feature = "imxrt1011", feature = "imxrt1015"))] + pub const ARM_HZ: u32 = 500_000_000; + + #[cfg(any(feature = "imxrt1064", feature = "imxrt1062", feature = "imxrt1061"))] pub const ARM_HZ: u32 = 600_000_000; /// Set the clock speed for the ARM core. This represents the base processor frequency. @@ -921,6 +925,30 @@ pub mod spi { LPSPI_PODF_6 = ccm::CBCMR::LPSPI_PODF::RW::LPSPI_PODF_6, /// 0b0111: divide by 8 LPSPI_PODF_7 = ccm::CBCMR::LPSPI_PODF::RW::LPSPI_PODF_7, + /// 0b1000: divide by 9 + #[cfg(features = "imxrt1011")] + LPSPI_PODF_8 = ccm::CBCMR::LPSPI_PODF::RW::LPSPI_PODF_8, + /// 0b1001: divide by 10 + #[cfg(features = "imxrt1011")] + LPSPI_PODF_9 = ccm::CBCMR::LPSPI_PODF::RW::LPSPI_PODF_9, + /// 0b1010: divide by 11 + #[cfg(features = "imxrt1011")] + LPSPI_PODF_10 = ccm::CBCMR::LPSPI_PODF::RW::LPSPI_PODF_10, + /// 0b1011: divide by 12 + #[cfg(features = "imxrt1011")] + LPSPI_PODF_11 = ccm::CBCMR::LPSPI_PODF::RW::LPSPI_PODF_11, + /// 0b1100: divide by 13 + #[cfg(features = "imxrt1011")] + LPSPI_PODF_12 = ccm::CBCMR::LPSPI_PODF::RW::LPSPI_PODF_12, + /// 0b1101: divide by 14 + #[cfg(features = "imxrt1011")] + LPSPI_PODF_13 = ccm::CBCMR::LPSPI_PODF::RW::LPSPI_PODF_13, + /// 0b1110: divide by 15 + #[cfg(features = "imxrt1011")] + LPSPI_PODF_14 = ccm::CBCMR::LPSPI_PODF::RW::LPSPI_PODF_14, + /// 0b1111: divide by 16 + #[cfg(features = "imxrt1011")] + LPSPI_PODF_15 = ccm::CBCMR::LPSPI_PODF::RW::LPSPI_PODF_15, } impl From for Frequency { diff --git a/imxrt-hal/src/dma.rs b/imxrt-hal/src/dma.rs index 4ad641ff..25c63321 100644 --- a/imxrt-hal/src/dma.rs +++ b/imxrt-hal/src/dma.rs @@ -34,7 +34,7 @@ //! handler, since we're enabling an interrupt when the receive completes. //! //! ```no_run -//! use imxrt1060_hal::dma::{Circular, Buffer, Linear, Peripheral, bidirectional_u16}; +//! use imxrt_hal::dma::{Circular, Buffer, Linear, Peripheral, bidirectional_u16}; //! //! // Circular buffers have alignment requirements //! #[repr(align(512))] @@ -44,7 +44,7 @@ //! static RX_BUFFER: Buffer<[u16; 256]> = Buffer::new([0; 256]); //! static TX_BUFFER: Align = Align(Buffer::new([0; 256])); //! -//! let mut peripherals = imxrt1060_hal::Peripherals::take().unwrap(); +//! let mut peripherals = imxrt_hal::Peripherals::take().unwrap(); //! //! // //! // SPI setup... @@ -52,8 +52,8 @@ //! //! let (_, _, _, spi4_builder) = peripherals.spi.clock( //! &mut peripherals.ccm.handle, -//! imxrt1060_hal::ccm::spi::ClockSelect::Pll2, -//! imxrt1060_hal::ccm::spi::PrescalarSelect::LPSPI_PODF_5, +//! imxrt_hal::ccm::spi::ClockSelect::Pll2, +//! imxrt_hal::ccm::spi::PrescalarSelect::LPSPI_PODF_5, //! ); //! //! let mut spi4 = spi4_builder.build( @@ -150,55 +150,314 @@ //! - Channel priority, and channel priority swapping //! - Channel chaining +#![allow(non_snake_case)] // Compatibility with RAL + mod buffer; +mod element; mod memcpy; pub(crate) mod peripheral; - -use imxrt_dma::Transfer; -pub use imxrt_dma::{Channel, Element, ErrorStatus}; +mod register; pub use buffer::{Buffer, Circular, CircularError, Drain, Linear, ReadHalf, WriteHalf}; +pub use element::Element; pub use memcpy::Memcpy; pub use peripheral::{helpers::*, Peripheral}; use crate::{ccm, ral}; +use core::{ + fmt::{self, Debug, Display}, + mem, +}; +pub use register::tcd::BandwidthControl; +use register::{DMARegisters, MultiplexerRegisters, Static, DMA, MULTIPLEXER}; + +/// A DMA channel +/// +/// DMA channels provide one-way transfers of data. They accept a source of data, +/// and a destination of data. They copy data from the source to the destination. +/// When the transfer is complete, a DMA channel signals completion by changing a +/// value in a register, or triggering an interrupt. +/// +/// DMA channels have very little public interface. They're best used when paired with a +/// [`Peripheral`](struct.Peripheral.html) or a [`Memcpy`](struct.Memcpy.html). +pub struct Channel { + /// Our channel number, expected to be between 0 to 31 + index: usize, + /// Reference to the DMA registers + registers: Static, + /// Reference to the DMA multiplexer + multiplexer: Static, +} + +impl Channel { + /// Set the channel's bandwidth control + /// + /// The bandwidth control will be used in any [`Memcpy`](struct.Memcpy.html) or + /// [`Peripheral`](struct.Peripheral.html) DMA transfers. + /// + /// - `None` disables bandwidth control (default setting) + /// - `Some(bwc)` sets the bandwidth control to `bwc` + pub fn set_bandwidth_control(&mut self, bandwidth: Option) { + let raw = BandwidthControl::raw(bandwidth); + let tcd = self.tcd(); + ral::modify_reg!(register::tcd, tcd, CSR, BWC: raw); + } + + /// Returns the DMA channel number + /// + /// Channels are unique and numbered within the half-open range `[0, 32)`. + pub fn channel(&self) -> usize { + self.index + } + + /// Allocates a DMA channel, and sets the initial state for + /// the channel. + fn new(index: usize) -> Self { + let channel = Channel { + index, + registers: DMA, + multiplexer: MULTIPLEXER, + }; + channel.registers.TCD[channel.index].reset(); + channel + } + + /// Returns a handle to this channel's transfer control descriptor + fn tcd(&self) -> ®ister::TransferControlDescriptor { + &self.registers.TCD[self.index] + } + + /// Prepare the source of a transfer; see [`Transfer`](struct.Transfer.html) for details. + /// + /// # Safety + /// + /// Address pointer must be valid for lifetime of the transfer. + unsafe fn set_source_transfer>, E: Element>(&mut self, transfer: T) { + let tcd = self.tcd(); + let transfer = transfer.into(); + ral::write_reg!(register::tcd, tcd, SADDR, transfer.address as u32); + ral::write_reg!(register::tcd, tcd, SOFF, transfer.offset); + ral::modify_reg!(register::tcd, tcd, ATTR, SSIZE: E::DATA_TRANSFER_ID, SMOD: transfer.modulo); + ral::write_reg!(register::tcd, tcd, SLAST, transfer.last_address_adjustment); + } + + /// Prepare the destination for a transfer; see [`Transfer`](struct.Transfer.html) for details. + /// + /// # Safety + /// + /// Address pointer must be valid for lifetime of the transfer. + unsafe fn set_destination_transfer>, E: Element>(&mut self, transfer: T) { + let tcd = self.tcd(); + let transfer = transfer.into(); + ral::write_reg!(register::tcd, tcd, DADDR, transfer.address as u32); + ral::write_reg!(register::tcd, tcd, DOFF, transfer.offset); + ral::modify_reg!(register::tcd, tcd, ATTR, DSIZE: E::DATA_TRANSFER_ID, DMOD: transfer.modulo); + ral::write_reg!( + register::tcd, + tcd, + DLAST_SGA, + transfer.last_address_adjustment + ); + } + + /// Set the number of *bytes* to transfer per minor loop + /// + /// Describes how many bytes we should transfer for each DMA service request. + fn set_minor_loop_bytes(&mut self, nbytes: u32) { + let tcd = self.tcd(); + ral::write_reg!(register::tcd, tcd, NBYTES, nbytes); + } + + /// Se the number of elements to move in each minor loop + /// + /// Describes how many elements we should transfer for each DMA service request. + fn set_minor_loop_elements(&mut self, len: usize) { + self.set_minor_loop_bytes((mem::size_of::() * len) as u32); + } + + /// Tells the DMA channel how many transfer iterations to perform + /// + /// A 'transfer iteration' is a read from a source, and a write to a destination, with + /// read and write sizes described by a minor loop. Each iteration requires a DMA + /// service request, either from hardware or from software. + fn set_transfer_iterations(&mut self, iterations: u16) { + let tcd = self.tcd(); + ral::write_reg!(register::tcd, tcd, CITER, iterations); + ral::write_reg!(register::tcd, tcd, BITER, iterations); + } -/// The number of DMA channels -pub const CHANNEL_COUNT: usize = 32; + /// Enable or disabling triggering from hardware + /// + /// If source is `Some(value)`, we trigger from hardware identified by the source identifier. + /// If `source` is `None`, we disable hardware triggering. + fn set_trigger_from_hardware(&mut self, source: Option) { + let chcfg = &self.multiplexer.chcfg[self.index]; + chcfg.write(0); + if let Some(source) = source { + chcfg.write(MultiplexerRegisters::ENBL | source); + } + } + + /// Set this DMA channel as always on + /// + /// Use `set_always_on()` so that the DMA multiplexer drives the transfer with no + /// throttling. Specifically, an "always-on" transfer will not need explicit re-activiation + /// between major loops. + /// + /// Use an always-on channel for memory-to-memory transfers, so that you don't need explicit + /// software re-activation to maintain the transfer. On the other hand, most peripheral transfers + /// should not use an always-on channel, since the peripheral should control the data flow through + /// explicit activation. + fn set_always_on(&mut self) { + let chcfg = &self.multiplexer.chcfg[self.index]; + chcfg.write(0); + chcfg.write(MultiplexerRegisters::ENBL | MultiplexerRegisters::A_ON); + } + + /// Returns `true` if the DMA channel is receiving a service signal from hardware + fn is_hardware_signaling(&self) -> bool { + self.registers.HRS.read() & (1 << self.index) != 0 + } + + /// Enable or disable the DMA's multiplexer request + /// + /// In this DMA implementation, all peripheral transfers and memcpy requests + /// go through the DMA multiplexer. So, this needs to be set for the multiplexer + /// to service the channel. + fn set_enable(&mut self, enable: bool) { + if enable { + self.registers.SERQ.write(self.index as u8); + } else { + self.registers.CERQ.write(self.index as u8); + } + } + + /// Returns `true` if this DMA channel generated an interrupt + fn is_interrupt(&self) -> bool { + self.registers.INT.read() & (1 << self.index) != 0 + } + + /// Clear the interrupt flag from this DMA channel + fn clear_interrupt(&mut self) { + self.registers.CINT.write(self.index as u8); + } + + /// Enable or disable 'disable on completion' + /// + /// 'Disable on completion' lets the DMA channel automatically clear the request signal + /// when it completes a transfer. + fn set_disable_on_completion(&mut self, dreq: bool) { + let tcd = self.tcd(); + ral::modify_reg!(register::tcd, tcd, CSR, DREQ: dreq as u16); + } + + /// Enable or disable interrupt generation when the transfer completes + /// + /// You're responsible for registering your interrupt handler. + pub fn set_interrupt_on_completion(&mut self, intr: bool) { + let tcd = self.tcd(); + ral::modify_reg!(register::tcd, tcd, CSR, INTMAJOR: intr as u16); + } + + /// Returns `true` if this channel's completion will generate an interrupt + pub fn is_interrupt_on_completion(&self) -> bool { + let tcd = self.tcd(); + ral::read_reg!(register::tcd, tcd, CSR, INTMAJOR == 1) + } + + /// Enable or disable interrupt generation when the transfer is half complete + /// + /// You're responsible for registering your interrupt handler. + pub fn set_interrupt_on_half(&mut self, intr: bool) { + let tcd = self.tcd(); + ral::modify_reg!(register::tcd, tcd, CSR, INTHALF: intr as u16); + } + + /// Returns `true` if this channel will generate an interrupt halfway through transfer + pub fn is_interrupt_on_half(&self) -> bool { + let tcd = self.tcd(); + ral::read_reg!(register::tcd, tcd, CSR, INTHALF == 1) + } + + /// Indicates if the DMA transfer has completed + fn is_complete(&self) -> bool { + let tcd = self.tcd(); + ral::read_reg!(register::tcd, tcd, CSR, DONE == 1) + } + + /// Clears completion indication + fn clear_complete(&mut self) { + self.registers.CDNE.write(self.index as u8); + } + + /// Indicates if the DMA channel is in an error state + fn is_error(&self) -> bool { + self.registers.ERR.read() & (1 << self.index) != 0 + } + + /// Clears the error flag + fn clear_error(&mut self) { + self.registers.CERR.write(self.index as u8); + } + + /// Indicates if this DMA channel is actively transferring data + fn is_active(&self) -> bool { + let tcd = self.tcd(); + ral::read_reg!(register::tcd, tcd, CSR, ACTIVE == 1) + } + + /// Indicates if this DMA channel is enabled + fn is_enabled(&self) -> bool { + self.registers.ERQ.read() & (1 << self.index) != 0 + } + + /// Returns the value from the **global** error status register + /// + /// It may reflect the last channel that produced an error, and that + /// may not be related to this channel. + fn error_status(&self) -> u32 { + self.registers.ES.read() + } + + /// Start a DMA transfer + /// + /// `start()` should be used to request service from the DMA controller. It's + /// necessary for in-memory DMA transfers. Do not use it for hardware-initiated + /// DMA transfers. DMA transfers that involve hardware will rely on the hardware + /// to request DMA service. + /// + /// Flag is automatically cleared by hardware after it's asserted. + fn start(&mut self) { + self.registers.SSRT.write(self.index as u8); + } +} /// An error when preparing a transfer #[derive(Debug)] #[non_exhaustive] -pub enum Error { +pub enum Error

{ /// There is already a scheduled transfer /// /// Cancel the transfer and try again. ScheduledTransfer, + /// The peripheral returned an error + Peripheral(P), /// Error setting up the DMA transfer Setup(ErrorStatus), } -/// Helper symbol to support DMA channel initialization -/// -/// We always provide users with an array of 32 channels. But, only the first `CHANNEL_COUNT` -/// channels are initialized. -const DMA_CHANNEL_INIT: [Option; 32] = [ - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, -]; - /// Unclocked, uninitialized DMA channels /// /// Use [`clock()`](struct.Unclocked.html#method.clock) to initialize and acquire all DMA channels /// /// ```no_run -/// let mut peripherals = imxrt1060_hal::Peripherals::take().unwrap(); +/// let mut peripherals = imxrt_hal::Peripherals::take().unwrap(); /// /// let mut dma_channels = peripherals.dma.clock(&mut peripherals.ccm.handle); /// let channel_27 = dma_channels[27].take().unwrap(); /// let channel_0 = dma_channels[0].take().unwrap(); /// ``` -pub struct Unclocked([Option; CHANNEL_COUNT]); +pub struct Unclocked([Option; 32]); impl Unclocked { pub(crate) fn new(dma: ral::dma0::Instance, mux: ral::dmamux::Instance) -> Self { // Explicitly dropping instances @@ -208,24 +467,134 @@ impl Unclocked { drop(dma); drop(mux); - Unclocked(DMA_CHANNEL_INIT) + Unclocked([ + None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, + ]) } /// Enable the clocks for the DMA peripheral /// - /// The return is an array of 32 channels. However, **only the first [`CHANNEL_COUNT`](constant.CHANNEL_COUNT.html) channels - /// are initialized to `Some(channel)`. The rest are `None`.** - /// - /// Users may take channels as needed. The index in the array maps to the DMA channel number. + /// The return is 32 channels, each being initialized as `Some(Channel)`. Users may take channels as needed. + /// The index in the array maps to the DMA channel number. pub fn clock(mut self, ccm: &mut ccm::Handle) -> [Option; 32] { let (ccm, _) = ccm.raw(); ral::modify_reg!(ral::ccm, ccm, CCGR5, CG3: 0x03); - for (idx, channel) in self.0.iter_mut().take(CHANNEL_COUNT).enumerate() { - // Safety: because we have the DMA instance, we assume that we own the DMA - // peripheral. That means we own all the DMA channels. - let mut chan = unsafe { Channel::new(idx) }; - chan.reset(); - *channel = Some(chan); + for (idx, channel) in self.0.iter_mut().enumerate() { + *channel = Some(Channel::new(idx)); } self.0 } } + +/// A wrapper around a DMA error status value +/// +/// The wrapper contains a copy of the DMA controller's +/// error status register at the point of an error. The +/// wrapper implements both `Debug` and `Display`. The +/// type may be printed to understand why there was a +/// DMA error. +#[derive(Clone, Copy)] +pub struct ErrorStatus { + /// The raw error status + es: u32, +} + +impl ErrorStatus { + const fn new(es: u32) -> Self { + ErrorStatus { es } + } +} + +impl Debug for ErrorStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "DMA_ES({:#010X})", self.es) + } +} + +impl Display for ErrorStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, + "DMA_ES: VLD {vld} ECX {ecx} GPE {gpe} CPE {cpe} ERRCHN {errchn} SAE {sae} SOE {soe} DAE {dae} DOE {doe} NCE {nce} SGE {sge} SBE {sbe} DBE {dbe}", + vld = (self.es >> 31) & 0x1, + ecx = (self.es >> 16) & 0x1, + gpe = (self.es >> 15) & 0x1, + cpe = (self.es >> 14) & 0x1, + errchn = (self.es >> 8) & 0x1F, + sae = (self.es >> 7) & 0x1, + soe = (self.es >> 6) & 0x1, + dae = (self.es >> 5) & 0x1, + doe = (self.es >> 4) & 0x1, + nce = (self.es >> 3) & 0x1, + sge = (self.es >> 2) & 0x1, + sbe = (self.es >> 1) & 0x1, + dbe = self.es & 0x1 + ) + } +} + +/// Describes a DMA transfer +/// +/// `Transfer` describes a source or a destination of a DMA transfer. See the member +/// documentation for details. +#[derive(Clone, Copy, Debug)] +struct Transfer { + /// The starting address for the DMA transfer + /// + /// If this describes a source, `address` will be the first + /// address read. If this describes a destination, `address` + /// will be the first address written. + address: *const E, + + /// Offsets to perform for each read / write of a memory address. + /// + /// When defining a transfer for a peripheral source or destination, + /// `offset` should be zero. Otherwise, `offset` should represent the + /// size of the data element, `E`. + /// + /// Negative (backwards) adjustments are permitted, if you'd like to read + /// a buffer backwards or something. + offset: i16, + + /* size: u16, // Not needed; captured in E: Element type */ + /// Defines the strategy for reading / writing linear or circular buffers + /// + /// `modulo` should be zero if this definition defines a transfer from linear + /// memory or a peripheral. `modulo` will be non-zero when defining a transfer + /// from a circular buffer. The non-zero value is the number of high bits to freeze + /// when performing address offsets (see `offset`). Given that we're only supporting + /// power-of-two buffer sizes, `modulo` will be `31 - clz(cap * sizeof(E))`, where `cap` is the + /// total size of the circular buffer, `clz` is "count leading zeros," and `sizeof(E)` is + /// the size of the element, in bytes. + modulo: u16, + + /// Perform any last-address adjustments when we complete the transfer + /// + /// Once we complete moving data from a linear buffer, we should set our pointer back to the + /// initial address. For this case, `last_address_adjustment` should be a negative number that + /// describes how may *bytes* to move backwards from our current address to reach our starting + /// address. Alternatively, it could describe how to move to a completely new address, like + /// a nearby buffer that we're using for a double-buffer. Or, set it to zero, which means "keep + /// your current position." "Keep your current position" is important when working with a + /// peripheral address! + last_address_adjustment: i32, +} + +impl Transfer { + fn hardware(address: *const E) -> Self { + Transfer { + address, + // Don't move the address pointer + offset: 0, + // We're not a circular buffer + modulo: 0, + // Don't move the address pointer + last_address_adjustment: 0, + } + } +} + +// It's OK to send a channel across an execution context. +// They can't be cloned or copied, so there's no chance of +// them being (mutably) shared. +unsafe impl Send for Channel {}