diff --git a/avr-hal-generic/src/lib.rs b/avr-hal-generic/src/lib.rs index 365bf3b21a..4416f18245 100644 --- a/avr-hal-generic/src/lib.rs +++ b/avr-hal-generic/src/lib.rs @@ -20,6 +20,7 @@ pub mod port; pub mod simple_pwm; pub mod spi; pub mod usart; +pub mod usart_spi; pub mod wdt; /// Prelude containing all HAL traits diff --git a/avr-hal-generic/src/spi.rs b/avr-hal-generic/src/spi.rs index 6a1bdbf154..04199ffb74 100644 --- a/avr-hal-generic/src/spi.rs +++ b/avr-hal-generic/src/spi.rs @@ -1,5 +1,5 @@ //! SPI Implementation -use crate::port; +use crate::{port, usart::UsartOps}; use core::marker::PhantomData; use embedded_hal::spi::{self, SpiBus}; @@ -200,6 +200,42 @@ pub struct Spi { _h: PhantomData, } +impl + Spi +where + USART: SpiOps + + UsartOps, port::Pin>, + SCLKPIN: port::PinOps, + MOSIPIN: port::PinOps, + MISOPIN: port::PinOps, +{ + /// Instantiate a USART based SPI with the registers, SCLK/MOSI/MISO pins, and settings, + /// with the internal pull-up enabled on the MISO pin. + /// + /// The pins are not actually used directly, but they are moved into the struct in + /// order to enforce that they are in the correct mode, and cannot be used by anyone + /// else while SPI is active. + pub fn new_from_usart( + p: USART, + sclk: port::Pin, + mosi: port::Pin, + miso: port::Pin, MISOPIN>, + settings: Settings, + ) -> Self { + let mut spi = Self { + p, + sclk, + mosi, + miso: miso.forget_imode(), + write_in_progress: false, + _cs: PhantomData, + _h: PhantomData, + }; + spi.p.raw_setup(&settings); + spi + } +} + impl Spi where SPI: SpiOps, diff --git a/avr-hal-generic/src/usart_spi.rs b/avr-hal-generic/src/usart_spi.rs new file mode 100644 index 0000000000..d271f57b7d --- /dev/null +++ b/avr-hal-generic/src/usart_spi.rs @@ -0,0 +1,132 @@ +//! MSPIM Implimentation +use crate::{port::PinOps, spi}; + +// This module just implements a macro for SpiOps, since underlyingly, the Spi type can still be used since it just needs SpiOps + +/// Dummy Pin for MPSPIM +pub struct UsartSPIDummyPin; + +impl PinOps for UsartSPIDummyPin { + type Dynamic = Self; + + fn into_dynamic(self) -> Self::Dynamic { + self + } + + unsafe fn out_set(&mut self) {} + + unsafe fn out_clear(&mut self) {} + + unsafe fn out_toggle(&mut self) {} + + unsafe fn out_get(&self) -> bool { + false + } + + unsafe fn in_get(&self) -> bool { + true + } + + unsafe fn make_output(&mut self) {} + + unsafe fn make_input(&mut self, _pull_up: bool) {} +} + +pub type UsartSpi = + spi::Spi; + +// Implement SpiOps trait for USART +#[macro_export] +macro_rules! add_usart_spi { + ( + hal: $HAL:ty, + peripheral: $USART_SPI:ty, + register_suffix: $n:expr, + sclk: $sclkpin:ty, + mosi: $mosipin:ty, + miso: $misopin:ty, + ) => { + $crate::paste::paste! { + // This is quite a messy way to get the doc string working properly... but it works! + #[doc = concat!("**Clock:** `", stringify!($sclkpin), "`
**MOSI:** `", stringify!($mosipin), "`
**MISO:** `", stringify!($misopin), "`")] + pub type [] = avr_hal_generic::usart_spi::UsartSpi<$HAL, $USART_SPI, $sclkpin, $mosipin, $misopin>; + + impl $crate::spi::SpiOps<$HAL, $sclkpin, $mosipin, $misopin, $crate::usart_spi::UsartSPIDummyPin> for $USART_SPI { + fn raw_setup(&mut self, settings: &$crate::spi::Settings) { + use $crate::hal::spi; + + // UBRRn must be zero at the time the transmitter is enabled. + self.[].write(|w| unsafe {w.bits(0)}); + + // We have to translate the character size register into the 2 bits which are the MSB/LSB and the phase + // 5 Bit Char = MSB and 1st + // 6 Bit Char = MSB and 2nd + // 7 Bit Char = LSB and 1st + // 8 Bit Char = LSB and 2nd + self.[].write(|w| { + w.[]().spi_master(); + + match settings.data_order { + $crate::spi::DataOrder::MostSignificantFirst => match settings.mode.phase { + spi::Phase::CaptureOnFirstTransition => w.[]().chr5(), + spi::Phase::CaptureOnSecondTransition => w.[]().chr6(), + }, + $crate::spi::DataOrder::LeastSignificantFirst => match settings.mode.phase { + spi::Phase::CaptureOnFirstTransition => w.[]().chr7(), + spi::Phase::CaptureOnSecondTransition => w.[]().chr8(), + }, + }; + + match settings.mode.polarity { + spi::Polarity::IdleLow => w.[]().clear_bit(), + spi::Polarity::IdleHigh => w.[]().set_bit(), + } + }); + + // Enable receiver and transmitter. + self.[].write(|w| w + .[]().set_bit() + .[]().set_bit() + ); + + // Set the clock divider for SPI clock. + // This must be done after the transmitter is enabled. + self.[].write(|w| unsafe { + match settings.clock { + $crate::spi::SerialClockRate::OscfOver2 => w.bits(0), + $crate::spi::SerialClockRate::OscfOver4 => w.bits(1), + $crate::spi::SerialClockRate::OscfOver8 => w.bits(3), + $crate::spi::SerialClockRate::OscfOver16 => w.bits(7), + $crate::spi::SerialClockRate::OscfOver32 => w.bits(15), + $crate::spi::SerialClockRate::OscfOver64 => w.bits(31), + $crate::spi::SerialClockRate::OscfOver128 => w.bits(63), + } + }); + } + + fn raw_release(&mut self) { + self.[].write(|w| w.[]().usart_async()); + self.[].reset(); + } + + fn raw_check_iflag(&self) -> bool { + self.[].read().[]().bit_is_set() + } + + fn raw_read(&self) -> u8 { + self.[].read().bits() + } + + fn raw_write(&mut self, byte: u8) { + self.[].write(|w| unsafe { w.bits(byte) }); + } + + fn raw_transaction(&mut self, byte: u8) -> u8 { + self.raw_write(byte); + while !self.raw_check_iflag() {} + self.raw_read() + } + } + } + }; +} diff --git a/examples/atmega2560/src/bin/atmega2560-usart_spi-feedback.rs b/examples/atmega2560/src/bin/atmega2560-usart_spi-feedback.rs new file mode 100644 index 0000000000..3957d0337e --- /dev/null +++ b/examples/atmega2560/src/bin/atmega2560-usart_spi-feedback.rs @@ -0,0 +1,75 @@ +//! This example demonstrates how to set up a SPI interface and communicate +//! over it. The physical hardware configuration consists of connecting a +//! jumper directly from pin `PB2` to pin `PB3`. +//! +//! Run the program using `cargo run`. +//! You should see it output the line `data: 42` repeatedly. +//! If the output you see is `data: 255`, you may need to check your jumper. + +#![no_std] +#![no_main] + +use atmega_hal::delay::Delay; +use atmega_hal::usart::{Baudrate, Usart}; +use atmega_hal::usart_spi; +use embedded_hal::delay::DelayNs; +use embedded_hal::spi::SpiBus; +use panic_halt as _; + +// Define core clock. This can be used in the rest of the project. +type CoreClock = atmega_hal::clock::MHz16; + +#[avr_device::entry] +fn main() -> ! { + let dp = atmega_hal::Peripherals::take().unwrap(); + let pins = atmega_hal::pins!(dp); + + let mut delay = Delay::::new(); + + // set up serial interface for text output + let mut serial = Usart::new( + dp.USART0, + pins.pe0, + pins.pe1.into_output(), + Baudrate::::new(57600), + ); + + // Create SPI interface. + let mut spi = usart_spi::Usart1Spi::new_from_usart( + dp.USART1, + pins.pd5.into_output(), + pins.pd3.into_output(), + pins.pd2.into_pull_up_input(), + atmega_hal::spi::Settings::default(), + ); + + // Other SPI examples for other USART's + + // let mut spi = usart_spi::Usart2Spi::new_from_usart( + // dp.USART2, + // pins.ph2.into_output(), + // pins.ph1.into_output(), + // pins.ph0.into_pull_up_input(), + // atmega_hal::spi::Settings::default(), + // ); + + // let mut spi = usart_spi::Usart3Spi::new_from_usart( + // dp.USART3, + // pins.pj2.into_output(), + // pins.pj1.into_output(), + // pins.pj0.into_pull_up_input(), + // atmega_hal::spi::Settings::default(), + // ); + + loop { + // Send a byte + let data_out: [u8; 1] = [42]; + let mut data_in: [u8; 1] = [0]; + // Send a byte + // Because MISO is connected to MOSI, the read data should be the same + spi.transfer(&mut data_in, &data_out).unwrap(); + + ufmt::uwriteln!(&mut serial, "data: {}\r", data_in[0]).unwrap(); + delay.delay_ms(1000); + } +} diff --git a/mcu/atmega-hal/src/lib.rs b/mcu/atmega-hal/src/lib.rs index e2ce9bf2e1..53f75426cf 100644 --- a/mcu/atmega-hal/src/lib.rs +++ b/mcu/atmega-hal/src/lib.rs @@ -145,6 +145,9 @@ pub mod eeprom; #[cfg(feature = "device-selected")] pub use eeprom::Eeprom; +#[cfg(feature = "device-selected")] +pub mod usart_spi; + pub struct Atmega; #[cfg(any(feature = "atmega48p", feature = "atmega168", feature = "atmega328p"))] diff --git a/mcu/atmega-hal/src/usart.rs b/mcu/atmega-hal/src/usart.rs index 7a8aaeaea7..6689ff5348 100644 --- a/mcu/atmega-hal/src/usart.rs +++ b/mcu/atmega-hal/src/usart.rs @@ -45,7 +45,8 @@ pub type UsartReader = feature = "atmega328p", feature = "atmega328pb", feature = "atmega1284p", - feature = "atmega164pa" + feature = "atmega164pa", + feature = "atmega48p" ))] pub type Usart0 = Usart< crate::pac::USART0, @@ -58,7 +59,8 @@ pub type Usart0 = Usart< feature = "atmega328p", feature = "atmega328pb", feature = "atmega1284p", - feature = "atmega164pa" + feature = "atmega164pa", + feature = "atmega48p" ))] avr_hal_generic::impl_usart_traditional! { hal: crate::Atmega, diff --git a/mcu/atmega-hal/src/usart_spi.rs b/mcu/atmega-hal/src/usart_spi.rs new file mode 100644 index 0000000000..2742e26aeb --- /dev/null +++ b/mcu/atmega-hal/src/usart_spi.rs @@ -0,0 +1,122 @@ +//! USART MSPIM implimentations +//! +//! The following list details how many USARTs and if the USARTs support MSPIM for each board choosable. +//! +//! | Board | USARTs | SPI | +//! |-------|--------|-----| +//! | `atmega48p` | 1 | Yes | +//! | `atmega164pa`| 2 | Yes | +//! | `atmega168` | 1 | Yes | +//! | `atmega328p` | 1 | Yes | +//! | `atmega328pb` | 1 | Yes | +//! | `atmega32a` | 1 | No | +//! | `atmega32u4` | 1 | Yes | +//! | `atmega2560` | 4 | Yes | +//! | `atmega128a` | 2 | No | +//! | `atmega1280` | 4 | Yes | +//! | `atmega1284p` | 2 | Yes | +//! | `atmega8` | 1 | No | +//! +//! # Example +//! +//! Complete example source code can be found in the repository: +//! [`atmega2560-usart_spi-feedback.rs`](https://github.com/Rahix/avr-hal/blob/main/examples/atmega2560/src/bin/atmega2560-usart_spi-feedback.rs) +//! +//! ``` +//! let dp = atmega_hal::Peripherals::take().unwrap(); +//! let pins = atmega_hal::pins!(dp); +//! +//! let mut spi = usart_spi::Usart1Spi::new_from_usart( +//! dp.USART1, +//! pins.pd5.into_output(), +//! pins.pd3.into_output(), +//! pins.pd2.into_pull_up_input(), +//! atmega_hal::spi::Settings::default(), +//! ); +//! +//! let data_out = b"Hello World!"; +//! let mut data_in = [0u8; 12]; +//! +//! spi.transfer(&mut data_in, data_out).unwrap(); +//! +//! ufmt::uwriteln!(&mut serial, "data: {:?}", data_in).unwrap(); +//! ``` + +// Suppress warning because it doesn't recognise us using it in macros properly. +#[allow(unused_imports)] +use crate::port; + +#[cfg(any(feature = "atmega1280", feature = "atmega2560"))] +avr_hal_generic::add_usart_spi! { + hal: crate::Atmega, + peripheral: crate::pac::USART0, + register_suffix: 0, + sclk: port::PE2, + mosi: port::PE1, + miso: port::PE0, +} + +#[cfg(any(feature = "atmega1280", feature = "atmega2560"))] +avr_hal_generic::add_usart_spi! { + hal: crate::Atmega, + peripheral: crate::pac::USART1, + register_suffix: 1, + sclk: port::PD5, + mosi: port::PD3, + miso: port::PD2, +} + +#[cfg(any(feature = "atmega1280", feature = "atmega2560"))] +avr_hal_generic::add_usart_spi! { + hal: crate::Atmega, + peripheral: crate::pac::USART2, + register_suffix: 2, + sclk: port::PH2, + mosi: port::PH1, + miso: port::PH0, +} + +#[cfg(any(feature = "atmega1280", feature = "atmega2560"))] +avr_hal_generic::add_usart_spi! { + hal: crate::Atmega, + peripheral: crate::pac::USART3, + register_suffix: 3, + sclk: port::PJ2, + mosi: port::PJ1, + miso: port::PJ0, +} + +#[cfg(any( + feature = "atmega168", + feature = "atmega328p", + feature = "atmega328pb", + feature = "atmega48p" +))] +avr_hal_generic::add_usart_spi! { + hal: crate::Atmega, + peripheral: crate::pac::USART0, + register_suffix: 0, + sclk: port::PD4, + mosi: port::PD1, + miso: port::PD0, +} + +#[cfg(any(feature = "atmega1284p", feature = "atmega164pa",))] +avr_hal_generic::add_usart_spi! { + hal: crate::Atmega, + peripheral: crate::pac::USART0, + register_suffix: 0, + sclk: port::PB0, + mosi: port::PD1, + miso: port::PD0, +} + +#[cfg(any(feature = "atmega1284p", feature = "atmega164pa",))] +avr_hal_generic::add_usart_spi! { + hal: crate::Atmega, + peripheral: crate::pac::USART1, + register_suffix: 1, + sclk: port::PD4, + mosi: port::PD3, + miso: port::PD2, +}