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

Add a HAL implementation for SPI master #13

Merged
merged 64 commits into from
Apr 23, 2020
Merged

Conversation

jonahbron
Copy link
Contributor

@jonahbron jonahbron commented Jul 3, 2019

In an attempt to execute #5 , I've begun on this skeleton of a SPI-implementing macro. I've only written a few lines of Rust, and never a macro, so this is based on reading the relevant documentation and following the examples of the other macros in the directory (namely serial.rs). I haven't even tried to compile it yet. It's a Work in Progress, and not yet ready to merge.

Please give any and all feedback. I will continue to work on fleshing out the details.

Note that I've intentionally substituted the original "master"/"slave" terminology for the alternate "primary"/"secondary" terminology. Hence "piso" and "posi" instead of "miso" and "mosi" respectively.

@Rahix
Copy link
Owner

Rahix commented Jul 3, 2019

Note that I've intentionally substituted the original "master"/"slave" terminology for the alternate "primary"/"secondary" terminology. Hence "piso" and "posi" instead of "miso" and "mosi" respectively.

Can you elaborate on why you did that?

@Rahix Rahix added the hal-impl label Jul 3, 2019
@jonahbron
Copy link
Contributor Author

jonahbron commented Jul 3, 2019

@Rahix Those terms carry a strong association with human slavery, usage of it can be insensitive to those who have been directly or indirectly impacted by that practice. See this section on Wikipedia on other projects that have migrated away from it and why. https://en.wikipedia.org/wiki/Master/slave_(technology)#Terminology_concerns

@jonahbron
Copy link
Contributor Author

@Rahix The concrete implementation is "done". I still need to test it and turn it into a macro, but please review it to make sure it seems sane at this point. Please offer any desired critique of the interface too.

@jonahbron
Copy link
Contributor Author

Pretty happy with the current state of the concrete implementation. Need to test it next, then make a macro out of it.

@jonahbron
Copy link
Contributor Author

Having trouble testing this implementation. I've been trying to verify it at a simple level by creating an example that connects POSI directly to PISO to read and write the same byte. However it's not working, it just reads a 0. Continuing debugging.

@jonahbron
Copy link
Contributor Author

I've simplified my example to cut out the SPI implementation entirely and use the registers directly, but it still does not work as expected.

#![no_std]
#![no_main]
#![feature(proc_macro_hygiene)]

extern crate panic_halt;
use arduino_uno::prelude::*;

#[no_mangle]
pub extern fn main() -> ! {
    let dp = arduino_uno::Peripherals::take().unwrap();

    let mut delay = arduino_uno::Delay::new();
    let mut pins = arduino_uno::Pins::new(
        dp.PORTB,
        dp.PORTC,
        dp.PORTD,
    );
    let peripheral = dp.SPI;
    let mut secondary_select = pins.d10.into_output(&mut pins.ddr);

    let mut serial = arduino_uno::Serial::new(
        dp.USART0,
        pins.d0,
        pins.d1.into_output(&mut pins.ddr),
        57600,
    );

    peripheral.spcr.write(|w| {
        w.spie().clear_bit();// disable SPI interrupt
        w.spe().set_bit();// enable SPI
        w.dord().clear_bit();// set up data order control bit
        w.mstr().set_bit();// set to primary mode
        w.cpol().clear_bit();// set up polarity control bit
        w.cpha().set_bit();// set up phase control bit
        w.spr().val_0x03()// set up clock rate control bit
    });
    peripheral.spsr.write(|w| w.spi2x().set_bit());// set up 2x clock rate status bit
    loop {
        ufmt::uwriteln!(&mut serial, "config: {}", peripheral.spcr.read().bits()).unwrap();
        secondary_select.set_low().unwrap();
        peripheral.spdr.write(|w| w.bits(0b10101010));
        while peripheral.spsr.read().spif().bit_is_clear() {}
        ufmt::uwriteln!(&mut serial, "response: {}\r", peripheral.spdr.read().bits()).unwrap();
        secondary_select.set_high().unwrap();
        delay.delay_ms(1000);
    }
}

Some output

config: 87
          waiting
response: 0
config: 87
          waiting
response: 0
config: 87
          waiting
response: 0
config: 87
          waiting
response: 0
config: 87
          waiting
response: 255
config: 87
          waiting
response: 0
config: 87
          waiting
response: 0
config: 87
          waiting
response: 0
config: 87
          waiting
response: 0

Oddly sometimes the byte will come back as 0b11111111. No idea why. I've tweaked a bunch of the control register settings and even checked that the config (87) matches what I expect according to the datasheet.

At this point I'm wondering if this is even possible (connecting PISO to POSI). I'll probably try integrating with the Ethernet module I have next. I wanted to wait as long as possible on that, since it's a less primitive test and will be more difficult to debug.

@jonahbron
Copy link
Contributor Author

I did make an attempt at getting the Ethernet module to work, but it wouldn't even initialize before panicking. According to this StackExchange answer though, my idea of looping PISO to POSI should work.

https://arduino.stackexchange.com/questions/66942/spi-test-by-connecting-mosi-to-miso/66950#66950

I'll continue debugging with that simple setup.

@jonahbron
Copy link
Contributor Author

Finally had an epiphany and solved the problem. The problem was that the POSI pin was not in output mode. Should have the SPI library working and ready soon now. Here's a super basic (working) example:

#![no_std]
#![no_main]
#![feature(proc_macro_hygiene)]
extern crate panic_halt;
use arduino_uno::prelude::*;
#[no_mangle]
pub extern fn main() -> ! {
    let dp = arduino_uno::Peripherals::take().unwrap();
    let mut delay = arduino_uno::Delay::new();
    let mut pins = arduino_uno::Pins::new(
        dp.PORTB,
        dp.PORTC,
        dp.PORTD,
    );
    pins.d10.into_output(&mut pins.ddr);// POSI pin must be made an ouptput
    pins.d11.into_output(&mut pins.ddr);// secondary select pin must be made an output
    let mut serial = arduino_uno::Serial::new(
        dp.USART0,
        pins.d0,
        pins.d1.into_output(&mut pins.ddr),
        57600,
    );
    dp.SPI.spcr.write(|w| {
        w.spie().clear_bit();
        w.spe().set_bit();// must enable SPI
        w.dord().clear_bit();
        w.mstr().set_bit();// must set to primary mode
        w.cpol().clear_bit();
        w.cpha().clear_bit();
        w.spr().val_0x00()
    });
    dp.SPI.spsr.write(|w| w.spi2x().clear_bit());

    loop {
        dp.SPI.spdr.write(|w| w.bits(0b10101010));
        while dp.SPI.spsr.read().spif().bit_is_clear() {}
        let read_data = dp.SPI.spdr.read().bits();

        ufmt::uwriteln!(&mut serial, "data: {}\r", read_data).unwrap();
        delay.delay_ms(1000);
    }
}

@jonahbron
Copy link
Contributor Author

Successfully moved the working SPI code into the atmega328p library and added an example that uses it. Next up I need to add more docs to the example, then move it back into the macro.

@jonahbron
Copy link
Contributor Author

Documentation is ready, all that's left is turning it into a macro.

@jonahbron
Copy link
Contributor Author

@Rahix Please see the comment I left above.

@jonahbron
Copy link
Contributor Author

jonahbron commented Apr 12, 2020

I updated the examples to use the serial.print method directly for the transmitted byte. It behaves perfectly on my Uno.

No idea why uwriteln is failing on a dynamic u8 parameter, but at least the important parts here are working.

This PR is ready as far as I'm concerned. Please LMK if there's anything else that needs to be tweaked.

@Rahix
Copy link
Owner

Rahix commented Apr 12, 2020

This does sound like a compiler issue ... We had something similar happen before, which I pushed a workaround for in commit cab8613 ("Add workaround for ufmt related codegen bug"). Which version of ufmt are you using right now? Is that commit in your branch as well?

@jonahbron
Copy link
Contributor Author

jonahbron commented Apr 13, 2020

This is from Cargo.lock

[[patch.unused]]
name = "ufmt"
version = "0.1.0-beta.5"
source = "git+https://github.com/Rahix/ufmt.git?rev=5a0f82991c74becb81415d3c2b74fba872496ab6#5a0f82991c74becb81415d3c2b74fba872496ab6"

I've pulled the latest from your master branch as of yesterday, so if your commit is there, I have it.

Edit: checked, your commit is included.

@Rahix
Copy link
Owner

Rahix commented Apr 13, 2020

Hm, not sure if this is correct. It says [[patch.unused]] in the first line you posted. I'll check my own build and see whether it is included for me.

@Rahix
Copy link
Owner

Rahix commented Apr 13, 2020

Damn, I see the same thing; the patched version is not included in the build. I'll dig a bit and check what causes this.

@Rahix
Copy link
Owner

Rahix commented Apr 13, 2020

I pushed commit 2cb668d ("Update to ufmt 0.1.0"). Can you try pulling that into your branch, then do cargo update and try again? If you hit a cargo error, try removing Cargo.lock and retrying. Hope this works ...

Copy link
Owner

@Rahix Rahix left a comment

Choose a reason for hiding this comment

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

A few small things, with those fixed I think we are ready for merge. :) If anything doesn't make much sense, let me know. I can try explaining it a bit more ...

avr-hal-generic/src/spi.rs Outdated Show resolved Hide resolved
avr-hal-generic/src/spi.rs Outdated Show resolved Hide resolved
avr-hal-generic/src/spi.rs Outdated Show resolved Hide resolved
avr-hal-generic/src/spi.rs Outdated Show resolved Hide resolved
@jonahbron
Copy link
Contributor Author

I pushed commit 2cb668d ("Update to ufmt 0.1.0"). Can you try pulling that into your branch, then do cargo update and try again? If you hit a cargo error, try removing Cargo.lock and retrying. Hope this works ...

I'll give that a try tonight.

Thank you for patiently explaining all of this to me. I'll work on another round of revisions tonight.

@jonahbron
Copy link
Contributor Author

@Rahix I applied your suggestions, and the patch to ufmt works perfectly.

@Rahix Rahix self-requested a review April 14, 2020 07:11
Comment on lines +197 to +200
fn read(&mut self) -> $crate::nb::Result<u8, Self::Error> {
self.flush()?;
Ok(self.peripheral.spdr.read().bits())
}
Copy link
Owner

Choose a reason for hiding this comment

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

Nice catch adding the flush() here! I guess you already read the docs but just for reference (for me and potential others): This works because every read has to happen after a send by the traits design.

I'm wondering whether we should add an assertion that this is upheld by the caller ... (which would only be enabled in debug builds)

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe, I'm inclined to leave it though. It will work if they read first, they'll just read 0b00000000. Plus we'd either have to use the is_write_in_progress flag in the assertion which has the disadvantage of only allowing one read after a write, OR add another boolean flag, which has a run-time impact. Not sure if the compiler can remove an entire field or not.

@Rahix
Copy link
Owner

Rahix commented Apr 15, 2020

I don't have much time today unfortunately, but hopefully I'll get around to setting up a logic-analyzer and testing this in depth in the coming days. Thank you so much for working on this (and sticking around for so long :D)!

@jonahd-g
Copy link
Contributor

Sounds good!

@jonahbron
Copy link
Contributor Author

How's it looking @Rahix ?

@Rahix Rahix self-requested a review April 23, 2020 12:56
Copy link
Owner

@Rahix Rahix left a comment

Choose a reason for hiding this comment

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

Seems to work fine from what I tested!

@Rahix Rahix changed the title SPI Implementation Add a HAL implementation for SPI master Apr 23, 2020
@Rahix Rahix merged commit 726f7f4 into Rahix:master Apr 23, 2020
@jonahd-g
Copy link
Contributor

Woohoo! Thanks @Rahix!

🎉 🎉 🎉

@Rahix Rahix mentioned this pull request Jun 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants