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
Show file tree
Hide file tree
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 Jun 27, 2019
2136e7c
Merge remote-tracking branch 'Rahix/master'
jonahbron Jul 2, 2019
823a5da
Added skeleton of SPI implementor macro
jonahbron Jul 3, 2019
18bb855
Fleshed out theoretical steps to read/write to SPI
jonahbron Jul 3, 2019
936bc8a
Fleshed out more details of SPI macro in comments
jonahbron Jul 3, 2019
24570cb
Temporarily moved to a non-macro implementation of SPI to make debugg…
jonahbron Jul 3, 2019
dd7cea9
Accepted peripheral in constructor and added data writing
jonahbron Jul 4, 2019
46a0cb3
Unwrapped secondary_select state changes
jonahbron Jul 5, 2019
5af1622
Finished fleshing out SPI implementation (testing pending)
jonahbron Jul 5, 2019
1dd920a
Refactored SPI code to be simpler, added doc comments
jonahbron Jul 6, 2019
79f5b68
Made simple SPI proof-of-concept for Uno
jonahbron Jul 12, 2019
1b4f916
Moved POC for SPI code back into atmega328p SPI library, added exampl…
jonahbron Jul 12, 2019
a796048
Removed POC example
jonahbron Jul 12, 2019
d2b922f
Added comments to SPI example
jonahbron Jul 12, 2019
021998b
Added better documentation to the SPI library
jonahbron Jul 12, 2019
87bec23
Moved SPI library into macro and invoked from atmega328p
jonahbron Jul 12, 2019
a09cf20
Removed commented-out manual implementation
jonahbron Jul 12, 2019
f160871
Added SPI support to ATMega32u4/Leonardo
jonahbron Jul 12, 2019
b8bf624
Updated readme with status of SPI
jonahbron Jul 12, 2019
311e775
Fixed pin comments in examples
jonahbron Jul 12, 2019
b54001e
Removed changes to cargo that were necessary to implement SPI manually
jonahbron Jul 12, 2019
787846d
Added SCLK pin to SPI constructor to allow output mode enforcement, u…
jonahbron Jul 20, 2019
1ce5a7e
Made SPI settings public so they can be changed by user
jonahbron Jul 20, 2019
7bfcdcc
Since SPI takes ownership of pins and so cannot share, removed redund…
jonahbron Jul 20, 2019
604414c
Merge branch 'master' of github.com:Rahix/avr-hal
jonahbron Oct 23, 2019
bd13c70
Wrapped newly unsafe function in an unsafe block
jonahbron Oct 23, 2019
5233da4
Moved SPI chip implementations to lib file, maintaining the same modu…
jonahd-g Feb 3, 2020
9c3bdcc
Removed alias, value cannot be moved twice
jonahd-g Feb 3, 2020
864f42f
Replaced empty SpiError enum with void::Void
jonahd-g Feb 3, 2020
103783c
Replaced bespoke polarity/phase enums with Embedded HAL's standard eq…
jonahd-g Feb 3, 2020
854ca0a
Made release() SPI method disable device before releasing peripheral …
jonahd-g Feb 3, 2020
bcc913f
Renamed piso/posi to miso/mosi to maintain consistency with external …
jonahd-g Feb 3, 2020
0878ef3
Merge pull request #1 from jonahd-google/master
jonahbron Feb 3, 2020
4101196
Removed unused SpiError enum
jonahd-g Feb 3, 2020
60db66d
Merge pull request #2 from jonahd-google/master
jonahbron Feb 3, 2020
ecea704
Imporoved wording of SPI constructor docs
jonahd-g Feb 6, 2020
f4cd0ed
Renamed ICSP pins on Leonardo
jonahd-g Feb 6, 2020
e2d46cb
Moved Settings outside of macro
jonahd-g Feb 6, 2020
74c11d0
Fixed docs for SPI example
jonahd-g Feb 6, 2020
2fcd7a8
Removed unused SpiError enum
jonahd-g Feb 6, 2020
1af06a6
Removed mention of USB since Leonardo has no USB
jonahd-g Feb 6, 2020
5902653
Merge branch 'master' of github.com:jonahd-google/avr-hal
jonahd-g Feb 6, 2020
9af129c
Fixed path to device in Uno SPI example
jonahd-g Feb 6, 2020
a31779b
Merge pull request #3 from jonahd-google/master
jonahbron Feb 6, 2020
beab667
Fixed name of pins in Leonardo SPI example
jonahd-g Apr 11, 2020
fa22d56
Added general-purpose derivable traits to settings structs/enums
jonahd-g Apr 11, 2020
248f4d1
Changed to proper use of NB to enable the program to do other things …
jonahd-g Apr 11, 2020
cdc4de5
Merge branch 'master' of github.com:Rahix/avr-hal
jonahd-g Apr 11, 2020
2cae2ce
Merge pull request #4 from jonahd-google/master
jonahbron Apr 11, 2020
faa58de
Fixed write-in-progress flag clearing
jonahd-g Apr 12, 2020
03ffc32
Updated SPI Feedback examples to correctly block the FullDuplex methods
jonahd-g Apr 12, 2020
32f1055
Merge pull request #5 from jonahd-google/master
jonahbron Apr 12, 2020
3d26634
Improved SPI feedback examples to print transmitted character to seri…
jonahd-g Apr 12, 2020
5a1de82
Merge pull request #6 from jonahd-google/master
jonahbron Apr 12, 2020
486cbd7
Ran cargo fmt on SPI examples
jonahd-g Apr 12, 2020
9c0a1ff
Merge pull request #7 from jonahd-google/master
jonahbron Apr 12, 2020
0700adc
Removed derived traits from macro definition
jonahd-g Apr 14, 2020
73e2668
Merge branch 'master' of github.com:Rahix/avr-hal
jonahd-g Apr 14, 2020
a04fbee
Simplified SPI feedback examples to use ufmt again
jonahd-g Apr 14, 2020
fa1da88
Merge pull request #8 from jonahd-google/master
jonahbron Apr 14, 2020
204fbfa
Changed implementation of SPI to match nb::Result contract correctly:…
jonahd-g Apr 14, 2020
7907026
Merge pull request #9 from jonahd-google/master
jonahbron Apr 14, 2020
bb50c07
Removed debug trait from SPI
jonahd-g Apr 14, 2020
be3bffb
Merge pull request #10 from jonahd-google/master
jonahbron Apr 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ The following peripherals are supported in `avr-hal-generic`:
- [x] A spinning delay implementation
- [x] `PORTx` peripherals as digital IO (v2)
- [x] A TWI based I2C implementation
- [X] SPI primary-mode implementation

### HAL Status
The chip-HAL crates currently support the following peripherals:
Expand All @@ -75,11 +76,13 @@ The chip-HAL crates currently support the following peripherals:
- [x] `PORTB`, `PORTC`, `PORTD` as digital IO (v2)
- [x] `USART0` for serial communication
- [x] I2C using `TWI`
- [x] SPI
* [`atmega32u4-hal`](./chips/atmega32u4-hal)
- [x] Spinning Delay
- [x] `PORTB`, `PORTC`, `PORTD`, `PORTE`, `PORTF` as digital IO (v2)
- [x] `USART1` for serial communication
- [x] I2C using `TWI`
- [x] SPI
* [`attiny85-hal`](./chips/attiny85-hal)
- [x] Spinning Delay
- [x] `PORTB` as digital IO (v2)
Expand All @@ -89,10 +92,10 @@ In `boards/` there are crates for the following hardware. Please note that this

* [Arduino Leonardo](./boards/arduino-leonardo)
- [Website](https://www.arduino.cc/en/Main/Arduino_BoardLeonardo)
- Support for basic digital IO
- Support for basic digital IO and SPI
* [Arduino Uno](./boards/arduino-uno)
- [Website](https://store.arduino.cc/usa/arduino-uno-rev3)
- Support for basic digital IO
- Support for basic digital IO and SPI
* [Adafruit Trinket (3V3 or 5V)](./boards/trinket) (**not** PRO!)
- [Website](https://learn.adafruit.com/introducing-trinket)
- Support for basic digital IO
Expand Down
1 change: 1 addition & 0 deletions avr-hal-generic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod delay;
pub mod port;
pub mod serial;
pub mod i2c;
pub mod spi;

/// Prelude containing all HAL traits
pub mod prelude {
Expand Down
203 changes: 203 additions & 0 deletions avr-hal-generic/src/spi.rs
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())
}
Comment on lines +197 to +200
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.

}
};
}
50 changes: 50 additions & 0 deletions boards/arduino-leonardo/examples/leonardo-spi-feedback.rs
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);
}
}
1 change: 1 addition & 0 deletions boards/arduino-leonardo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod pins;
pub use atmega32u4_hal::atmega32u4;
pub use crate::atmega32u4::Peripherals;
pub use atmega32u4_hal::prelude;
pub use atmega32u4_hal::spi;
pub use crate::pins::*;

pub type Delay = hal::delay::Delay<hal::clock::MHz16>;
Expand Down
14 changes: 13 additions & 1 deletion boards/arduino-leonardo/src/pins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,23 @@ avr_hal_generic::impl_board_pins! {
pub d13: portc::pc7::PC7,
/// `RX`
///
/// Led for indicating inbound data
/// Led for indicating inbound data. Also the CS pin.
pub led_rx: portb::pb0::PB0,
/// `TX`
///
/// Led for indicating outbound data
pub led_tx: portd::pd5::PD5,
/// `SCLK`
///
/// ICSP SCLK pin
pub sck: portb::pb1::PB1,
/// `MOSI`
///
/// ICSP MOSI pin
pub mosi: portb::pb2::PB2,
/// `MISO`
///
/// ICSP MISO pin
pub miso: portb::pb3::PB3,
}
}
55 changes: 55 additions & 0 deletions boards/arduino-uno/examples/uno-spi-feedback.rs
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);
}
}
1 change: 1 addition & 0 deletions boards/arduino-uno/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod pins;
pub use atmega328p_hal::atmega328p;
pub use crate::atmega328p::Peripherals;
pub use atmega328p_hal::prelude;
pub use atmega328p_hal::spi;
pub use crate::pins::*;

pub type Delay = hal::delay::Delay<hal::clock::MHz16>;
Expand Down
Loading