-
Notifications
You must be signed in to change notification settings - Fork 226
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a HAL implementation for SPI master
Implement embedded-hal's FullDuplex SPI master trait for the dedicated SPI peripheral built into a lot of AVR chips.
- Loading branch information
Showing
11 changed files
with
448 additions
and
3 deletions.
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(), | ||
); | ||
|
||
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.