-
Notifications
You must be signed in to change notification settings - Fork 223
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
UsartSpi
Support
#562
base: main
Are you sure you want to change the base?
UsartSpi
Support
#562
Changes from all commits
4a110c9
8ac3a86
2cd0cbd
82ad09a
ab5fad0
5debde9
a37cbc2
c5b076f
9ddb92b
b88cf04
d9bfd46
597b073
54990d0
46f4183
893f775
8fa453f
3593202
0f0c38c
7b21e5c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,132 @@ | ||||||||
//! MSPIM Implimentation | ||||||||
use crate::{port::PinOps, spi}; | ||||||||
|
||||||||
// This module just impliments 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 = todo!(); | ||||||||
|
||||||||
fn into_dynamic(self) -> Self::Dynamic { | ||||||||
todo!() | ||||||||
} | ||||||||
|
||||||||
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) {} | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
} | ||||||||
|
||||||||
pub type UsartSpi<H, USART, SCLKPIN, MOSIPIN, MISOPIN> = | ||||||||
spi::Spi<H, USART, SCLKPIN, MOSIPIN, MISOPIN, UsartSPIDummyPin>; | ||||||||
|
||||||||
// Impliment 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, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
) => { | ||||||||
$crate::paste::paste! { | ||||||||
// This is quite a messy way to get the doc string working properly... but it works! | ||||||||
#[doc = concat!("**Clock:** `", stringify!($sclkpin), "`<br>**MOSI:** `", stringify!($mosipin), "`<br> **MISO:** `", stringify!($misopin), "`<br> **CS:** `", stringify!($cspin), "`")] | ||||||||
pub type [<Usart $n Spi>] = avr_hal_generic::usart_spi::UsartSpi<$HAL, $USART_SPI, $sclkpin, $mosipin, $misopin>; | ||||||||
|
||||||||
impl $crate::spi::SpiOps<$HAL, $sclkpin, $mosipin, $misopin, $cspin> for $USART_SPI { | ||||||||
CoolSlimbo marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
fn raw_setup(&mut self, settings: &crate::spi::Settings) { | ||||||||
use $crate::hal::spi; | ||||||||
|
||||||||
// Setup control registers | ||||||||
// We start by setting the UBBRn to 0 | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't comment what code is doing, that should be obvious from reading the line below. Please comment why it is doing this — what's the purpose of setting UBBRn to 0 here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unsure of the purpose. But code examples in the data sheet all do so, explicitly setting it to 0, before setting the baudrate (which I will update the calculation for) |
||||||||
self.[<ubrr $n>].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.[<ucsr $n c>].write(|w| { | ||||||||
w.[<umsel $n>]().spi_master(); | ||||||||
|
||||||||
match settings.data_order { | ||||||||
crate::spi::DataOrder::MostSignificantFirst => match settings.mode.phase { | ||||||||
spi::Phase::CaptureOnFirstTransition => w.[<ucsz $n>]().chr5(), | ||||||||
spi::Phase::CaptureOnSecondTransition => w.[<ucsz $n>]().chr6(), | ||||||||
}, | ||||||||
crate::spi::DataOrder::LeastSignificantFirst => match settings.mode.phase { | ||||||||
spi::Phase::CaptureOnFirstTransition => w.[<ucsz $n>]().chr7(), | ||||||||
spi::Phase::CaptureOnSecondTransition => w.[<ucsz $n>]().chr8(), | ||||||||
}, | ||||||||
}; | ||||||||
|
||||||||
match settings.mode.polarity { | ||||||||
spi::Polarity::IdleLow => w.[<ucpol $n>]().clear_bit(), | ||||||||
spi::Polarity::IdleHigh => w.[<ucpol $n>]().set_bit(), | ||||||||
} | ||||||||
}); | ||||||||
|
||||||||
// Enable receiver and transmitter, and also the rec interrupt. | ||||||||
self.[<ucsr $n b>].write(|w| w | ||||||||
.[<txen $n>]().set_bit() | ||||||||
.[<rxen $n>]().set_bit() | ||||||||
); | ||||||||
|
||||||||
// Set the clock divider for SPI clock. | ||||||||
self.[<ubrr $n>].write(|w| { | ||||||||
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.[<ucsr $n c>].write(|w| w.[<umsel $n>]().usart_async()); | ||||||||
self.[<ucsr $n b>].reset(); | ||||||||
} | ||||||||
|
||||||||
fn raw_check_iflag(&self) -> bool { | ||||||||
self.[<ucsr $n a>].read().[<rxc $n>]().bit_is_set() | ||||||||
} | ||||||||
|
||||||||
fn raw_read(&self) -> u8 { | ||||||||
self.[<udr $n>].read().bits() | ||||||||
} | ||||||||
|
||||||||
fn raw_write(&mut self, byte: u8) { | ||||||||
self.[<udr $n>].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() | ||||||||
} | ||||||||
} | ||||||||
} | ||||||||
}; | ||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
//! 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::<crate::CoreClock>::new(); | ||
|
||
// set up serial interface for text output | ||
let mut serial = Usart::new( | ||
dp.USART0, | ||
pins.pe0, | ||
pins.pe1.into_output(), | ||
Baudrate::<crate::CoreClock>::new(57600), | ||
); | ||
|
||
// Create SPI interface. | ||
let (mut spi, _) = usart_spi::Usart1Spi::new( | ||
dp.USART1, | ||
pins.pd5.into_output(), | ||
pins.pd3.into_output(), | ||
pins.pd2.into_pull_up_input(), | ||
pins.pd4.into_output().downgrade(), | ||
atmega_hal::spi::Settings::default(), | ||
); | ||
|
||
/* Other SPI examples for other USART's | ||
let (mut spi, _) = usart_spi::Usart2Spi::new( | ||
dp.USART2, | ||
pins.ph2.into_output(), | ||
pins.ph1.into_output(), | ||
pins.ph0.into_pull_up_input(), | ||
pins.pd4.into_output().downgrade(), | ||
atmega_hal::spi::Settings::default(), | ||
); | ||
|
||
let (mut spi, _) = usart_spi::Usart3Spi::new( | ||
dp.USART3, | ||
pins.pj2.into_output(), | ||
pins.pj1.into_output(), | ||
pins.pj0.into_pull_up_input(), | ||
pins.pd4.into_output().downgrade(), | ||
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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
//! 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 | | ||
|
||
// Supress 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, | ||
cs: port::Dynamic, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, this is quite a hack. We really don't need the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was done because I impliment SpiOps to cheap out on work, and not rewrite everything. I can either reimplement a new spiops, or, force the use of a DummyPin internally (so users don’t have to worry about it) via the DummyPin crate (I believe that’s its name) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would be fine with defining a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An alternative to the Dummy CS Pin (and what I believe my original thought process was), is how it is, where it's dynamic, meaning the CS can be handled by the driver in some which way, by manual toggle instead of SPI handling it. |
||
} | ||
|
||
#[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, | ||
cs: port::Dynamic, | ||
} | ||
|
||
#[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, | ||
cs: port::Dynamic, | ||
} | ||
|
||
#[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, | ||
cs: port::Dynamic, | ||
} | ||
|
||
#[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, | ||
cs: port::Dynamic, | ||
} | ||
|
||
#[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, | ||
cs: port::Dynamic, | ||
} | ||
|
||
#[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, | ||
cs: port::Dynamic, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found this in port.rs, gave it a try and at least it built. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my side this is fine. This pin-type is non-functional anyway - it just exists to bypass API problems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it turns out I could not build examples, as I did not know how to instantiate
UsartSPIDummyPin
.I was able to hack the examples into compiling by implementing a separate new for USART SPI, though it doesn't feel great.
To be honest, Spi taking a CS pin only makes sense for Spi slaves, as master could possibly have 0-N CS pins.
@Rahix is slave mode even supported? How would you feel about changing the API? Maybe split
new
intonew_master
andnew_slave
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spi
only takes the CS pin to enforce a hardware restriction, it actually doesn't do anything with the CS pin related to "CS" operation at all. This is an unfortunate side-effect of the AVR SPI peripheral design. You can read up more on this in #27.Actually, I had the same thought as you here: We need the Dummy Pin to make the type-system happy, but this should be hidden from the user if at all possible. The easiest way to hide it is by adding a new constructor, like the one you showcased here. I think that's the best option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the reference, I did not know about this behaviour 🤦♂️
My remaining issue with the separate constructor is how to enforce that the user passes actual USART peripheral? Because, as it stands, the following compiles...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, can we add a trait bound on
UsartOps
maybe? If not, I'd introduce a new marker traitUsartSpiDevice
that is implemented alongsideSpiOps
for USART peripherals. We can then add a trait bound on this marker trait to the new constructor.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding a trait bound on
UsartOps
seems to do it. I'll test on hardware and, if everything goes well, make a PR as you advised.