From 1ce913978aff87912b976016e4533c8614e68b90 Mon Sep 17 00:00:00 2001
From: jsjolund <j.sjolund@gmail.com>
Date: Fri, 22 Dec 2017 15:19:50 +0100
Subject: [PATCH] SPI support, add LSM9DS1 example

---
 examples/spi1.rs | 132 +++++++++++++++++++++
 src/lib.rs       |   2 +
 src/spi.rs       | 301 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 435 insertions(+)
 create mode 100644 examples/spi1.rs
 create mode 100644 src/spi.rs

diff --git a/examples/spi1.rs b/examples/spi1.rs
new file mode 100644
index 0000000..ba39e88
--- /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 96938c9..96a1ca9 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 0000000..76dda2f
--- /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);
-- 
GitLab