diff --git a/examples/spi1.rs b/examples/spi1.rs new file mode 100644 index 0000000000000000000000000000000000000000..ba39e88cf64d44ca2ffc28cc546a4184ec5927ef --- /dev/null +++ b/examples/spi1.rs @@ -0,0 +1,132 @@ +//! Interfacing the LSM9DS1 3D accelerometer, gyroscope, and magnetometer +//! using SPI3. +//! +//! Using the SparkFun LSM9DS1 breakout board, connect: +//! PA_8 -> CS_AG (Chip Select for accelerometer/gyro) +//! PA_9 -> CS_M (Chip Select for magnetometer) +//! PB_3 -> SCL (SPI clock) +//! PB_4 -> SDO_M and SDO_AG (Combined SPI MISO) +//! PB_5 -> SDA (SPI MOSI) + +#![deny(warnings)] +#![feature(proc_macro)] +#![no_std] + +extern crate cortex_m; +extern crate cortex_m_rtfm as rtfm; +extern crate f4; +extern crate stm32f40x; + +use f4::Spi; +use f4::prelude::*; +use rtfm::{app, Threshold}; +use stm32f40x::GPIOA; + +app! { + device: f4::stm32f40x, + + idle: { + resources: [SPI3, GPIOA], + }, +} + +fn init(p: init::Peripherals) { + // Setup CS pins + { + // Power on GPIOA + p.RCC.ahb1enr.modify(|_, w| w.gpioaen().set_bit()); + // Set PA_8 and PA_9 as outputs + p.GPIOA + .moder + .modify(|_, w| w.moder8().bits(1).moder9().bits(1)); + // Use pull-ups + p.GPIOA + .pupdr + .modify(|_, w| unsafe { w.pupdr8().bits(1).pupdr9().bits(1) }); + // Highest output speed + p.GPIOA + .ospeedr + .modify(|_, w| w.ospeedr8().bits(3).ospeedr9().bits(3)); + // Default to high (CS disabled) + p.GPIOA + .odr + .modify(|_, w| w.odr8().bit(true).odr9().bit(true)); + } + + // Init the SPI peripheral + let spi = Spi(p.SPI3); + spi.init(p.GPIOA, p.GPIOB, p.RCC); + + // For the LSM9DS1, the second clock transition is + // the first data capture edge + // RM0368 20.5.1 + p.SPI3.cr1.modify(|_, w| w.cpha().set_bit()); +} + +fn enable_m(gpioa: &mut GPIOA) { + gpioa.odr.modify(|_, w| w.odr9().bit(false)); +} + +fn disable_m(gpioa: &mut GPIOA) { + gpioa.odr.modify(|_, w| w.odr9().bit(true)); +} + +fn enable_ag(gpioa: &mut GPIOA) { + gpioa.odr.modify(|_, w| w.odr8().bit(false)); +} + +fn disable_ag(gpioa: &mut GPIOA) { + gpioa.odr.modify(|_, w| w.odr8().bit(true)); +} + +fn send_receive(spi: &f4::Spi<f4::stm32f40x::SPI3>, addr: u8) -> u8 { + // Send and receive using the hal crate + while spi.send(addr).is_err() {} + loop { + if let Ok(_) = spi.read() { + break; + } + } + while spi.send(0).is_err() {} + loop { + if let Ok(byte) = spi.read() { + break byte; + } + } +} + +fn to_read_address(sub_address: u8) -> u8 { + 0x80 | (sub_address & 0x3F) +} + +fn idle(_t: &mut Threshold, r: idle::Resources) -> ! { + // Registers to read + const WHO_AM_I_AG: u8 = 0x0F; + const WHO_AM_I_M: u8 = 0x0F; + + // Expected answers + const ANS_AG: u8 = 0x68; + const ANS_M: u8 = 0x3D; + + let spi = Spi(&*r.SPI3); + spi.enable(); + + // Read accelerometer/gyro identity + enable_ag(r.GPIOA); + let ans_ag = send_receive(&spi, to_read_address(WHO_AM_I_AG)); + disable_ag(r.GPIOA); + // Read magnetometer identity + enable_m(r.GPIOA); + let ans_m = send_receive(&spi, to_read_address(WHO_AM_I_M)); + disable_m(r.GPIOA); + + spi.disable(); + + // Program will panic if these are incorrect + assert_eq!(ans_ag, ANS_AG); + assert_eq!(ans_m, ANS_M); + + loop { + rtfm::wfi(); + } +} diff --git a/src/lib.rs b/src/lib.rs index 96938c9b75293e3f4bc896c279534ebf1093063e..96a1ca9e362522fdfd2b39ebda537f588ae75d14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,7 @@ pub mod time; pub mod pwm; pub mod capture; pub mod clock; +pub mod spi; pub mod frequency; use frequency::*; @@ -54,6 +55,7 @@ pub use serial::U8Writer; pub use timer::{Channel, Timer}; pub use pwm::Pwm; pub use capture::Capture; +pub use spi::Spi; /// println over semihosting #[macro_export] diff --git a/src/spi.rs b/src/spi.rs new file mode 100644 index 0000000000000000000000000000000000000000..76dda2f192b2fd8baaf598bd26becab375af538b --- /dev/null +++ b/src/spi.rs @@ -0,0 +1,301 @@ +//! Serial Peripheral Interface +//! +//! You can use the `Spi` interface with these SPI instances +//! +//! # SPI1 +//! +//! - NSS = PA4 +//! - SCK = PA5 +//! - MISO = PA6 +//! - MOSI = PA7 +//! +//! # SPI2 +//! +//! - NSS = PB12 +//! - SCK = PB13 +//! - MISO = PB14 +//! - MOSI = PB15 +//! +//! # SPI3 +//! +//! - NSS = PA15 +//! - SCK = PB3 +//! - MISO = PB4 +//! - MOSI = PB5 +//! +use core::any::{Any, TypeId}; +use core::ptr; + +use hal; +use nb; +use stm32f40x::{SPI1, SPI2, SPI3, GPIOA, GPIOB, RCC}; + +/// SPI result +pub type Result<T> = ::core::result::Result<T, nb::Error<Error>>; + +/// SPI error +#[derive(Debug)] +pub enum Error { + /// Overrun occurred + Overrun, + /// Mode fault occurred + ModeFault, + /// CRC error + Crc, + #[doc(hidden)] _Extensible, +} + +/// Serial Peripheral Interface +pub struct Spi<'a, T>(pub &'a T) +where + T: 'a; + +/// Serial Peripheral Interface +macro_rules! impl_Spi { + ($S:ident) => { + impl<'a> Spi<'a, $S> + { + /// Initializes the SPI + pub fn init(&self, + gpioa: &GPIOA, // TODO: Make these optional/implement custom init for each SPI + gpiob: &GPIOB, + rcc: &RCC) { + let spi = self.0; + + if spi.get_type_id() == TypeId::of::<SPI1>() { + // enable SPI1, GPIOA + rcc.apb2enr.modify(|_, w| { + w.spi1en().set_bit() + }); + rcc.ahb1enr.modify(|_, w| { + w.gpioaen().set_bit() + }); + + // NSS = PA4 = Alternate function push pull + // SCK = PA5 = Alternate function push pull + // MISO = PA6 = Alternate function open drain + // MOSI = PA7 = Alternate function push pull + + // DM00102166 - Alternate function AF5, Table 9 + gpioa.afrl.modify(|_, w| + w.afrl4().bits(5) + .afrl5().bits(5) + .afrl6().bits(5) + .afrl7().bits(5)); + // RM0368 8.3 Table 23 + // Highest output speed + gpioa.ospeedr.modify(|_, w| + w.ospeedr4().bits(0b11) + .ospeedr5().bits(0b11) + .ospeedr6().bits(0b11) + .ospeedr7().bits(0b11)); + // Alternate function mode + gpioa.moder.modify(|_, w| + w.moder4().bits(2) + .moder5().bits(2) + .moder6().bits(2) + .moder7().bits(2)); + // Push pull, MISO open drain + gpioa.otyper.modify(|_, w| + w.ot4().clear_bit() + .ot5().clear_bit() + .ot6().set_bit() + .ot7().clear_bit() + ); + // No pull up/down except MISO + gpioa.pupdr.modify(|_, w| unsafe { + w.pupdr4().bits(0) + .pupdr5().bits(0) + .pupdr6().bits(1) + .pupdr7().bits(0) + }); + + } else if spi.get_type_id() == TypeId::of::<SPI2>() { + // enable SPI2, GPIOB + rcc.apb1enr.modify(|_, w| { + w.spi2en().set_bit() + }); + rcc.ahb1enr.modify(|_, w| { + w.gpioben().set_bit() + }); + + // NSS = PB12 = Alternate function push pull + // SCK = PB13 = Alternate function push pull + // MISO = PB14 = Alternate function open drain + // MOSI = PB15 = Alternate function push pull + + // DM00102166 - Alternate function AF5, Table 9 + gpiob.afrh.modify(|_, w| unsafe { + w.afrh12().bits(5) + .afrh13().bits(5) + .afrh14().bits(5) + .afrh15().bits(5) + }); + // RM0368 8.3 Table 23 + // Highest output speed + gpiob.ospeedr.modify(|_, w| unsafe { + w.ospeedr12().bits(0b11) + .ospeedr13().bits(0b11) + .ospeedr14().bits(0b11) + .ospeedr15().bits(0b11) + }); + // Alternate function mode + gpiob.moder.modify(|_, w| unsafe { + w.moder12().bits(2) + .moder13().bits(2) + .moder14().bits(2) + .moder15().bits(2) + }); + // Push pull, MISO open drain + gpiob.otyper.modify(|_, w| + w.ot12().clear_bit() + .ot13().clear_bit() + .ot14().set_bit() + .ot15().clear_bit() + ); + // No pull up/down except MISO + gpiob.pupdr.modify(|_, w| unsafe { + w.pupdr12().bits(0) + .pupdr13().bits(0) + .pupdr14().bits(1) + .pupdr15().bits(0) + }); + + } else if spi.get_type_id() == TypeId::of::<SPI3>() { + // enable SPI3, GPIOA/B + rcc.apb1enr.modify(|_, w| { + w.spi3en().set_bit() + }); + rcc.ahb1enr.modify(|_, w| { + w.gpioaen().set_bit() + }); + rcc.ahb1enr.modify(|_, w| { + w.gpioben().set_bit() + }); + + // NSS = PA15 = Alternate function push pull + // SCK = PB3 = Alternate function push pull + // MISO = PB4 = Alternate function open drain + // MOSI = PB5 = Alternate function push pull + + // DM00102166 - Alternate function AF6, Table 9 + gpioa.afrh.modify(|_, w| w.afrh15().bits(5)); + gpiob.afrl.modify(|_, w| unsafe { + w.afrl3().bits(6) + .afrl4().bits(6) + .afrl5().bits(6) + }); + // RM0368 8.3 Table 23 + // Highest output speed + gpioa.ospeedr.modify(|_, w| w.ospeedr15().bits(0b11)); + gpiob.ospeedr.modify(|_, w| unsafe { + w.ospeedr3().bits(0b11) + .ospeedr4().bits(0b11) + .ospeedr5().bits(0b11) + }); + // Alternate function mode + gpioa.moder.modify(|_, w| w.moder15().bits(2)); + gpiob.moder.modify(|_, w| unsafe { + w.moder3().bits(2) + .moder4().bits(2) + .moder5().bits(2) + }); + // Push pull, MISO open drain + gpioa.otyper.modify(|_, w| w.ot15().clear_bit()); + gpiob.otyper.modify(|_, w| + w.ot3().clear_bit() + .ot4().set_bit() + .ot5().clear_bit()); + // No pull up/down except MISO + gpioa.pupdr.modify(|_, w|unsafe { + w.pupdr15().bits(0)}); + gpiob.pupdr.modify(|_, w|unsafe { + w + .pupdr3().bits(0) + .pupdr4().bits(1) + .pupdr5().bits(0) + }); + } + + // enable SS output + spi.cr2.write(|w| w.ssoe().set_bit()); + + // RM0368 20.5.1 SPI control register 1 + spi.cr1.write(|w| unsafe { + w.br().bits(0b100) // CLK / 32 prescaler + .ssi().set_bit() // NSS + .ssm().set_bit() // Software chip select + .mstr().set_bit() // Master SPI mode + .cpol().set_bit() // CK to 1 when idle + .lsbfirst().clear_bit() // MSB first + .dff().clear_bit() // 8 bit frames + .bidimode().clear_bit() // 2-line unidirectional mode + }); + } + + /// Disables the SPI bus + /// + /// **NOTE** This drives the NSS pin high + pub fn disable(&self) { + self.0.cr1.modify(|_, w| w.spe().clear_bit()) + } + + /// Enables the SPI bus + /// + /// **NOTE** This drives the NSS pin low + pub fn enable(&self) { + self.0.cr1.modify(|_, w| w.spe().set_bit()) + } + + } + + impl<'a> hal::Spi<u8> for Spi<'a, $S> + { + type Error = Error; + + fn read(&self) -> Result<u8> { + let spi1 = self.0; + let sr = spi1.sr.read(); + + if sr.ovr().bit_is_set() { + Err(nb::Error::Other(Error::Overrun)) + } else if sr.modf().bit_is_set() { + Err(nb::Error::Other(Error::ModeFault)) + } else if sr.crcerr().bit_is_set() { + Err(nb::Error::Other(Error::Crc)) + } else if sr.rxne().bit_is_set() { + Ok(unsafe { + ptr::read_volatile(&spi1.dr as *const _ as *const u8) + }) + } else { + Err(nb::Error::WouldBlock) + } + } + + fn send(&self, byte: u8) -> Result<()> { + let spi1 = self.0; + let sr = spi1.sr.read(); + + if sr.ovr().bit_is_set() { + Err(nb::Error::Other(Error::Overrun)) + } else if sr.modf().bit_is_set() { + Err(nb::Error::Other(Error::ModeFault)) + } else if sr.crcerr().bit_is_set() { + Err(nb::Error::Other(Error::Crc)) + } else if sr.txe().bit_is_set() { + // NOTE(write_volatile) see note above + unsafe { + ptr::write_volatile(&spi1.dr as *const _ as *mut u8, byte) + } + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } + } + } +} + +impl_Spi!(SPI1); +impl_Spi!(SPI2); +impl_Spi!(SPI3);