Skip to content
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

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open

UsartSpi Support #562

wants to merge 19 commits into from

Conversation

CoolSlimbo
Copy link

PR regarding issue #561.

This is just so I have the PR made for now, work still needs to be made on it.

So far, USART0 is implemented for most of the atmega mc's. However, they are not tested, as I am lacking something to test it with.

Needs to be tested. Will test via Arudino UNO when I move them ig
@@ -31,6 +31,66 @@ avr_hal_generic::impl_spi! {
cs: port::PB0,
}

#[cfg(any(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Devices where UART port can be a SPI port

@stappersg
Copy link
Contributor

Idea, maybe a wild idea: The new code in one #[cfg(any( ))] block?

Anyway: Success with testing.

@CoolSlimbo
Copy link
Author

Wait. It actually worked on hardware? Part of me is surprised lol

I'll probably move it into its own module, but can't use one config, because they align to different ports between devices.

@CoolSlimbo CoolSlimbo changed the title UsartSpi Support **UNTESTED** UsartSpi Support Jun 23, 2024
@CoolSlimbo
Copy link
Author

Currently added in (I think) at minimum all the USART0 implimentations. But there's still a large portion to go, and finding the XCK pin requires getting the datasheet for each.

@Rahix Rahix linked an issue Jun 23, 2024 that may be closed by this pull request
@Rahix Rahix added hal-api API design for the different components of avr-hal hal-generic Related to MCU generic parts of avr-hal labels Jun 23, 2024
@CoolSlimbo
Copy link
Author

Implements USART, correctly, on all devices. Should past CI successfully.

An example could be in order, however, I am lacking any SPI slaves, so I cannot test this myself.

@Rahix
Copy link
Owner

Rahix commented Jun 27, 2024

An example could be in order, however, I am lacking any SPI slaves, so I cannot test this myself.

SPI can be tested very easily by looping back MOSI to MISO (so TX to RX in this case). We also have this in our real SPI example, maybe it makes sense to add an example that does the same for UsartSpi?

… doesn't have the right pins. Will fix that soon. If someone else can test would be great.
@Rahix
Copy link
Owner

Rahix commented Jul 7, 2024

Hey, there seem to be some compiler errors, as seen in CI. Can you take a look?

@armandas
Copy link
Contributor

armandas commented Jul 24, 2024

I went on and tested the atmega2560-usart_spi-feedback example. I tested USART1, USART2 and USART3 and they all work. USART0 is connected to USB, so I did not test that.

My diff to get it to compile:

diff --git a/examples/atmega2560/src/bin/atmega2560-usart_spi-feedback.rs b/examples/atmega2560/src/bin/atmega2560-usart_spi-feedback.rs
index 3a2d145..bce2ad4 100644
--- a/examples/atmega2560/src/bin/atmega2560-usart_spi-feedback.rs
+++ b/examples/atmega2560/src/bin/atmega2560-usart_spi-feedback.rs
@@ -36,12 +36,12 @@ fn main() -> ! {
 
     // Create SPI interface.
     let (mut spi, _) = usart_spi::Usart1Spi::new(
-        dp.SPI,
+        dp.USART1,
         pins.pd5.into_output(),
         pins.pd3.into_output(),
         pins.pd2.into_pull_up_input(),
         pins.pd4.into_output().downgrade(),
-        spi::Settings::default(),
+        atmega_hal::spi::Settings::default(),
     );
 
     loop {

Other configs, for reference:

USART2:

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

USART3:

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

(Tested)[Rahix#562 (comment)].

Introduces points for other examples commented in.
@armandas
Copy link
Contributor

@Rahix I was wondering if it would make sense to re-export configuration structs and enums defined in spi from usart_spi?

@Rahix
Copy link
Owner

Rahix commented Jul 25, 2024

I was wondering if it would make sense to re-export configuration structs and enums defined in spi from usart_spi?

To be honest, I would prefer not to add such re-exports. The more paths there are to reach an item the more convoluted user code will get.

use $crate::hal::spi;

// Setup control registers
// We start by setting the UBBRn to 0
Copy link
Owner

Choose a reason for hiding this comment

The 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?

Copy link
Author

Choose a reason for hiding this comment

The 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)

Comment on lines 59 to 64
// Enable receiver and transmitter, and also the rec interrupt.
self.[<ucsr $n b>].write(|w| w
.[<txen $n>]().set_bit()
.[<rxen $n>]().set_bit()
.[<rxcie $n>]().set_bit()
);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't enable the interrupt by default - it should always be explicitly enabled by the user through a method call, see e.g.

/// Enable the interrupt for [`Event`].
pub fn listen(&mut self, event: Event) {
self.p.raw_interrupt(event, true);
}
/// Disable the interrupt for [`Event`].
pub fn unlisten(&mut self, event: Event) {
self.p.raw_interrupt(event, false);
}

You don't need to implement this here, though — it should be part of the spi module.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate on what you mean by this?

I understand why to remove it (idk why it was there to begin with), but do you want me to add methods to change the interrupts? Or something other, because I don't understand "it should be part of the spi module"

Comment on lines 70 to 73
fn raw_release(&mut self) {
// Probably a better way to "release" the SPI interface, but from the datasheet, this is what they suggest, so ig it works
self.[<ucsr $n c>].write(|w| w.[<umsel $n>]().usart_async());
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the USART code, you also need to disable the USART peripheral here:

fn raw_deinit(&mut self) {
// Wait for any ongoing transfer to finish.
$crate::nb::block!(self.raw_flush()).ok();
self.[<ucsr $n b>].reset();
}

(The UCSRnB reset is the important part).

With that included, you can drop your comment.

sclk: port::PE2,
mosi: port::PE1,
miso: port::PE0,
cs: port::Dynamic,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, this is quite a hack. We really don't need the cs dance for MSPIM so it would be nice to somehow wrangle the SPI module to not require a CS pin for MSPIM...

Copy link
Author

Choose a reason for hiding this comment

The 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)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be fine with defining a UsartSpiDummyCs pin type that needs to be passed to Spi::new() to sidestep this. Pulling in an external crate for this is not a good idea, I think.

Copy link
Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

@armandas armandas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I messed with the code for a bit and got it to compile. Don't have a board on hand at the moment, but will try to run the code this week.

Comment on lines +10 to +14
type Dynamic = todo!();

fn into_dynamic(self) -> Self::Dynamic {
todo!()
}
Copy link
Contributor

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?

Suggested change
type Dynamic = todo!();
fn into_dynamic(self) -> Self::Dynamic {
todo!()
}
type Dynamic = Self;
fn into_dynamic(self) -> Self::Dynamic {
self
}

Copy link
Owner

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.

Copy link
Contributor

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.

impl<H, USART, SCLKPIN, MOSIPIN, MISOPIN, UsartSPIDummyPin> Spi<H, USART, SCLKPIN, MOSIPIN, MISOPIN, UsartSPIDummyPin>
where
    USART: SpiOps<H, SCLKPIN, MOSIPIN, MISOPIN, UsartSPIDummyPin>,
    SCLKPIN: port::PinOps,
    MOSIPIN: port::PinOps,
    MISOPIN: port::PinOps,
{
    /// Instantiate an SPI with the registers, SCLK/MOSI/MISO/CS 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.  CS is placed into a `ChipSelectPin` instance and given
    /// back so that its output state can be changed as needed.
    pub fn new_from_usart(
        p: USART,
        sclk: port::Pin<port::mode::Output, SCLKPIN>,
        mosi: port::Pin<port::mode::Output, MOSIPIN>,
        miso: port::Pin<port::mode::Input<port::mode::PullUp>, 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
    }
}

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 into new_master and new_slave?

Copy link
Owner

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.

Copy link
Contributor

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

let mut spi = spi::Spi::new_from_usart(
    dp.SPI,
    pins.pb1.into_output(),
    pins.pb2.into_output(),
    pins.pb3.into_pull_up_input(),
    spi::Settings::default(),
);

Copy link
Owner

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 trait UsartSpiDevice that is implemented alongside SpiOps for USART peripherals. We can then add a trait bound on this marker trait to the new constructor.

Copy link
Contributor

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.


unsafe fn make_output(&mut self) {}

unsafe fn make_input(&mut self, pull_up: bool) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
unsafe fn make_input(&mut self, pull_up: bool) {}
unsafe fn make_input(&mut self, _pull_up: bool) {}

register_suffix: $n:expr,
sclk: $sclkpin:ty,
mosi: $mosipin:ty,
miso: $misopin:ty,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
miso: $misopin:ty,
miso: $misopin:ty,
cs: $cspin:ty,

@Rahix
Copy link
Owner

Rahix commented Oct 3, 2024

BTW, @armandas, if you want to pick up this work while @CoolSlimbo isn't around, feel free to open a new PR based on top of this one.

armandas added a commit to armandas/avr-hal that referenced this pull request Oct 5, 2024
armandas added a commit to armandas/avr-hal that referenced this pull request Oct 5, 2024
@armandas armandas mentioned this pull request Oct 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hal-api API design for the different components of avr-hal hal-generic Related to MCU generic parts of avr-hal
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Utilising the atmega2560's "USART in SPI Mode"
4 participants