-
Notifications
You must be signed in to change notification settings - Fork 226
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
Changes from all commits
Commits
Show all changes
64 commits
Select commit
Hold shift + click to select a range
08ed9e3
Updated readme with latest supported hardware
jonahbron 2136e7c
Merge remote-tracking branch 'Rahix/master'
jonahbron 823a5da
Added skeleton of SPI implementor macro
jonahbron 18bb855
Fleshed out theoretical steps to read/write to SPI
jonahbron 936bc8a
Fleshed out more details of SPI macro in comments
jonahbron 24570cb
Temporarily moved to a non-macro implementation of SPI to make debugg…
jonahbron dd7cea9
Accepted peripheral in constructor and added data writing
jonahbron 46a0cb3
Unwrapped secondary_select state changes
jonahbron 5af1622
Finished fleshing out SPI implementation (testing pending)
jonahbron 1dd920a
Refactored SPI code to be simpler, added doc comments
jonahbron 79f5b68
Made simple SPI proof-of-concept for Uno
jonahbron 1b4f916
Moved POC for SPI code back into atmega328p SPI library, added exampl…
jonahbron a796048
Removed POC example
jonahbron d2b922f
Added comments to SPI example
jonahbron 021998b
Added better documentation to the SPI library
jonahbron 87bec23
Moved SPI library into macro and invoked from atmega328p
jonahbron a09cf20
Removed commented-out manual implementation
jonahbron f160871
Added SPI support to ATMega32u4/Leonardo
jonahbron b8bf624
Updated readme with status of SPI
jonahbron 311e775
Fixed pin comments in examples
jonahbron b54001e
Removed changes to cargo that were necessary to implement SPI manually
jonahbron 787846d
Added SCLK pin to SPI constructor to allow output mode enforcement, u…
jonahbron 1ce5a7e
Made SPI settings public so they can be changed by user
jonahbron 7bfcdcc
Since SPI takes ownership of pins and so cannot share, removed redund…
jonahbron 604414c
Merge branch 'master' of github.com:Rahix/avr-hal
jonahbron bd13c70
Wrapped newly unsafe function in an unsafe block
jonahbron 5233da4
Moved SPI chip implementations to lib file, maintaining the same modu…
jonahd-g 9c3bdcc
Removed alias, value cannot be moved twice
jonahd-g 864f42f
Replaced empty SpiError enum with void::Void
jonahd-g 103783c
Replaced bespoke polarity/phase enums with Embedded HAL's standard eq…
jonahd-g 854ca0a
Made release() SPI method disable device before releasing peripheral …
jonahd-g bcc913f
Renamed piso/posi to miso/mosi to maintain consistency with external …
jonahd-g 0878ef3
Merge pull request #1 from jonahd-google/master
jonahbron 4101196
Removed unused SpiError enum
jonahd-g 60db66d
Merge pull request #2 from jonahd-google/master
jonahbron ecea704
Imporoved wording of SPI constructor docs
jonahd-g f4cd0ed
Renamed ICSP pins on Leonardo
jonahd-g e2d46cb
Moved Settings outside of macro
jonahd-g 74c11d0
Fixed docs for SPI example
jonahd-g 2fcd7a8
Removed unused SpiError enum
jonahd-g 1af06a6
Removed mention of USB since Leonardo has no USB
jonahd-g 5902653
Merge branch 'master' of github.com:jonahd-google/avr-hal
jonahd-g 9af129c
Fixed path to device in Uno SPI example
jonahd-g a31779b
Merge pull request #3 from jonahd-google/master
jonahbron beab667
Fixed name of pins in Leonardo SPI example
jonahd-g fa22d56
Added general-purpose derivable traits to settings structs/enums
jonahd-g 248f4d1
Changed to proper use of NB to enable the program to do other things …
jonahd-g cdc4de5
Merge branch 'master' of github.com:Rahix/avr-hal
jonahd-g 2cae2ce
Merge pull request #4 from jonahd-google/master
jonahbron faa58de
Fixed write-in-progress flag clearing
jonahd-g 03ffc32
Updated SPI Feedback examples to correctly block the FullDuplex methods
jonahd-g 32f1055
Merge pull request #5 from jonahd-google/master
jonahbron 3d26634
Improved SPI feedback examples to print transmitted character to seri…
jonahd-g 5a1de82
Merge pull request #6 from jonahd-google/master
jonahbron 486cbd7
Ran cargo fmt on SPI examples
jonahd-g 9c0a1ff
Merge pull request #7 from jonahd-google/master
jonahbron 0700adc
Removed derived traits from macro definition
jonahd-g 73e2668
Merge branch 'master' of github.com:Rahix/avr-hal
jonahd-g a04fbee
Simplified SPI feedback examples to use ufmt again
jonahd-g fa1da88
Merge pull request #8 from jonahd-google/master
jonahbron 204fbfa
Changed implementation of SPI to match nb::Result contract correctly:…
jonahd-g 7907026
Merge pull request #9 from jonahd-google/master
jonahbron bb50c07
Removed debug trait from SPI
jonahd-g be3bffb
Merge pull request #10 from jonahd-google/master
jonahbron File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
//! SPI Implementation | ||
|
||
pub use embedded_hal::spi; | ||
|
||
/// Oscillator Clock Frequency division options. Controls both SPR and SPI2X register bits. | ||
#[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
pub enum SerialClockRate { | ||
OscfOver2, | ||
OscfOver4, | ||
OscfOver8, | ||
OscfOver16, | ||
OscfOver32, | ||
OscfOver64, | ||
OscfOver128, | ||
} | ||
|
||
/// Order of data transmission, either MSB first or LSB first | ||
#[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
pub enum DataOrder { | ||
MostSignificantFirst, | ||
LeastSignificantFirst, | ||
} | ||
|
||
/// Settings to pass to Spi. | ||
/// | ||
/// Easiest way to initialize is with | ||
/// `Settings::default()`. Otherwise can be instantiated with alternate | ||
/// settings directly. | ||
#[derive(Clone, PartialEq, Eq)] | ||
pub struct Settings { | ||
pub data_order: DataOrder, | ||
pub clock: SerialClockRate, | ||
pub mode: spi::Mode, | ||
} | ||
|
||
impl Default for Settings { | ||
fn default() -> Self { | ||
Settings { | ||
data_order: DataOrder::MostSignificantFirst, | ||
clock: SerialClockRate::OscfOver4, | ||
mode: spi::Mode { | ||
polarity: spi::Polarity::IdleLow, | ||
phase: spi::Phase::CaptureOnSecondTransition, | ||
}, | ||
} | ||
} | ||
} | ||
|
||
/// Implement traits for a SPI interface | ||
#[macro_export] | ||
macro_rules! impl_spi { | ||
( | ||
$(#[$spi_attr:meta])* | ||
pub struct $Spi:ident { | ||
peripheral: $SPI:ty, | ||
pins: { | ||
sclk: $sclkmod:ident::$SCLK:ident, | ||
mosi: $mosimod:ident::$MOSI:ident, | ||
miso: $misomod:ident::$MISO:ident, | ||
} | ||
} | ||
) => { | ||
|
||
use $crate::void::Void; | ||
use $crate::hal::spi; | ||
pub use avr_hal::spi::*; | ||
|
||
type SCLK = $sclkmod::$SCLK<$crate::port::mode::Output>; | ||
type MOSI = $mosimod::$MOSI<$crate::port::mode::Output>; | ||
type MISO = $misomod::$MISO<$crate::port::mode::Input<$crate::port::mode::PullUp>>; | ||
|
||
/// Behavior for a SPI interface. | ||
/// | ||
/// Stores the SPI peripheral for register access. In addition, it takes | ||
/// ownership of the MOSI and MISO pins to ensure they are in the correct mode. | ||
/// Instantiate with the `new` method. | ||
$(#[$spi_attr])* | ||
pub struct $Spi { | ||
peripheral: $SPI, | ||
sclk: SCLK, | ||
mosi: MOSI, | ||
miso: MISO, | ||
settings: Settings, | ||
is_write_in_progress: bool, | ||
} | ||
|
||
/// Implementation-specific behavior of the struct, including setup/tear-down | ||
impl $Spi { | ||
/// Instantiate an SPI with the registers, SCLK/MOSI/MISO pins, and settings | ||
/// | ||
/// 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(peripheral: $SPI, sclk: SCLK, mosi: MOSI, miso: MISO, settings: Settings) -> $Spi { | ||
let spi = Spi { | ||
peripheral, | ||
sclk, | ||
mosi, | ||
miso, | ||
settings, | ||
is_write_in_progress: false, | ||
}; | ||
spi.setup(); | ||
spi | ||
} | ||
|
||
/// Disable the SPI device and release ownership of the peripheral | ||
/// and pins. Instance can no-longer be used after this is | ||
/// invoked. | ||
pub fn release(self) -> ($SPI, SCLK, MOSI, MISO) { | ||
self.peripheral.spcr.write(|w| { | ||
w.spe().clear_bit() | ||
}); | ||
(self.peripheral, self.sclk, self.mosi, self.miso) | ||
} | ||
|
||
/// Write a byte to the data register, which begins transmission | ||
/// automatically. | ||
fn write(&mut self, byte: u8) { | ||
self.is_write_in_progress = true; | ||
self.peripheral.spdr.write(|w| unsafe { w.bits(byte) }); | ||
} | ||
|
||
/// Check if write flag is set, and return a WouldBlock error if it is not. | ||
fn flush(&mut self) -> $crate::nb::Result<(), $crate::void::Void> { | ||
if self.is_write_in_progress { | ||
if self.peripheral.spsr.read().spif().bit_is_set() { | ||
self.is_write_in_progress = false; | ||
} else { | ||
return Err($crate::nb::Error::WouldBlock); | ||
} | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// Sets up the control/status registers with the right settings for this secondary device | ||
fn setup(&self) { | ||
// set up control register | ||
self.peripheral.spcr.write(|w| { | ||
// enable SPI | ||
w.spe().set_bit(); | ||
// Set to primary mode | ||
w.mstr().set_bit(); | ||
// set up data order control bit | ||
match self.settings.data_order { | ||
DataOrder::MostSignificantFirst => w.dord().clear_bit(), | ||
DataOrder::LeastSignificantFirst => w.dord().set_bit(), | ||
}; | ||
// set up polarity control bit | ||
match self.settings.mode.polarity { | ||
spi::Polarity::IdleHigh => w.cpol().set_bit(), | ||
spi::Polarity::IdleLow => w.cpol().clear_bit(), | ||
}; | ||
// set up phase control bit | ||
match self.settings.mode.phase { | ||
spi::Phase::CaptureOnFirstTransition => w.cpha().clear_bit(), | ||
spi::Phase::CaptureOnSecondTransition => w.cpha().set_bit(), | ||
}; | ||
// set up clock rate control bit | ||
match self.settings.clock { | ||
SerialClockRate::OscfOver2 => w.spr().val_0x00(), | ||
SerialClockRate::OscfOver4 => w.spr().val_0x00(), | ||
SerialClockRate::OscfOver8 => w.spr().val_0x01(), | ||
SerialClockRate::OscfOver16 => w.spr().val_0x01(), | ||
SerialClockRate::OscfOver32 => w.spr().val_0x02(), | ||
SerialClockRate::OscfOver64 => w.spr().val_0x02(), | ||
SerialClockRate::OscfOver128 => w.spr().val_0x03(), | ||
} | ||
}); | ||
// set up 2x clock rate status bit | ||
self.peripheral.spsr.write(|w| match self.settings.clock { | ||
SerialClockRate::OscfOver2 => w.spi2x().set_bit(), | ||
SerialClockRate::OscfOver4 => w.spi2x().clear_bit(), | ||
SerialClockRate::OscfOver8 => w.spi2x().set_bit(), | ||
SerialClockRate::OscfOver16 => w.spi2x().clear_bit(), | ||
SerialClockRate::OscfOver32 => w.spi2x().set_bit(), | ||
SerialClockRate::OscfOver64 => w.spi2x().clear_bit(), | ||
SerialClockRate::OscfOver128 => w.spi2x().set_bit(), | ||
}); | ||
} | ||
} | ||
|
||
/// FullDuplex trait implementation, allowing this struct to be provided to | ||
/// drivers that require it for operation. Only 8-bit word size is supported | ||
/// for now. | ||
impl $crate::hal::spi::FullDuplex<u8> for $Spi { | ||
type Error = $crate::void::Void; | ||
|
||
/// Sets up the device for transmission and sends the data | ||
fn send(&mut self, byte: u8) -> $crate::nb::Result<(), Self::Error> { | ||
self.flush()?; | ||
self.write(byte); | ||
Ok(()) | ||
} | ||
|
||
/// Reads and returns the response in the data register | ||
fn read(&mut self) -> $crate::nb::Result<u8, Self::Error> { | ||
self.flush()?; | ||
Ok(self.peripheral.spdr.read().bits()) | ||
} | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
//! This example demonstrates how to set up a SPI interface and communicate | ||
//! over it. The physical hardware configuation consists of connecting a | ||
//! jumper directly from ICSP pin 10 to ICSP pin 11. | ||
//! | ||
//! Once this program is written to the board, you can use the board's serial | ||
//! connection to see the output. You should see it output the line | ||
//! `data: 15` repeatedly (aka 0b00001111). If the output you see is | ||
//! `data: 255`, you may need to check your jumper. | ||
|
||
#![no_std] | ||
#![no_main] | ||
#![feature(proc_macro_hygiene)] | ||
extern crate panic_halt; | ||
use arduino_leonardo::prelude::*; | ||
use arduino_leonardo::spi::{Settings, Spi}; | ||
use nb::block; | ||
#[no_mangle] | ||
pub extern "C" fn main() -> ! { | ||
let dp = arduino_leonardo::Peripherals::take().unwrap(); | ||
let mut delay = arduino_leonardo::Delay::new(); | ||
let mut pins = arduino_leonardo::Pins::new(dp.PORTB, dp.PORTC, dp.PORTD, dp.PORTE); | ||
|
||
let mut serial = arduino_leonardo::Serial::new( | ||
dp.USART1, | ||
pins.d0, | ||
pins.d1.into_output(&mut pins.ddr), | ||
57600, | ||
); | ||
|
||
pins.led_rx.into_output(&mut pins.ddr); // SS must be set to output mode. | ||
|
||
// Create SPI interface. | ||
let mut spi = Spi::new( | ||
dp.SPI, | ||
pins.sck.into_output(&mut pins.ddr), | ||
pins.mosi.into_output(&mut pins.ddr), | ||
pins.miso.into_pull_up_input(&mut pins.ddr), | ||
Settings::default(), | ||
); | ||
|
||
loop { | ||
// Send a byte | ||
block!(spi.send(0b00001111)).unwrap(); | ||
// Because MISO is connected to MOSI, the read data should be the same | ||
let data = block!(spi.read()).unwrap(); | ||
|
||
ufmt::uwriteln!(&mut serial, "data: {}\r", data).unwrap(); | ||
delay.delay_ms(1000); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
//! This example demonstrates how to set up a SPI interface and communicate | ||
//! over it. The physical hardware configuation consists of connecting a | ||
//! jumper directly from pin `~11` to pin `~12`. | ||
//! | ||
//! Once this program is written to the board, the serial output can be | ||
//! accessed with | ||
//! | ||
//! ``` | ||
//! sudo screen /dev/ttyACM0 57600 | ||
//! ``` | ||
//! | ||
//! You should see it output the line `data: 15` repeatedly (aka 0b00001111). | ||
//! If the output you see is `data: 255`, you may need to check your jumper. | ||
|
||
#![no_std] | ||
#![no_main] | ||
#![feature(proc_macro_hygiene)] | ||
extern crate panic_halt; | ||
use arduino_uno::prelude::*; | ||
use arduino_uno::spi::{Settings, Spi}; | ||
use nb::block; | ||
#[no_mangle] | ||
pub extern "C" 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); | ||
// set up serial interface for text output | ||
let mut serial = arduino_uno::Serial::new( | ||
dp.USART0, | ||
pins.d0, | ||
pins.d1.into_output(&mut pins.ddr), | ||
57600, | ||
); | ||
|
||
pins.d10.into_output(&mut pins.ddr); // SS must be set to output mode. | ||
|
||
// Create SPI interface. | ||
let mut spi = Spi::new( | ||
dp.SPI, | ||
pins.d13.into_output(&mut pins.ddr), | ||
pins.d11.into_output(&mut pins.ddr), | ||
pins.d12.into_pull_up_input(&mut pins.ddr), | ||
Settings::default(), | ||
jonahbron marked this conversation as resolved.
Show resolved
Hide resolved
|
||
); | ||
|
||
loop { | ||
// Send a byte | ||
block!(spi.send(0b00001111)).unwrap(); | ||
// Because MISO is connected to MOSI, the read data should be the same | ||
let data = block!(spi.read()).unwrap(); | ||
|
||
ufmt::uwriteln!(&mut serial, "data: {}\r", data).unwrap(); | ||
delay.delay_ms(1000); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
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)
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.
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.