From 3db793e9d62fa5cca8b01da192586a10e7131a49 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 6 Dec 2018 23:04:26 +0100 Subject: [PATCH] First release of stm32f0xx-hal based on stm32f042-hal Signed-off-by: Daniel Egger --- .cargo/config | 8 + .gitignore | 5 + Cargo.toml | 53 +++ README.md | 33 ++ examples/blinky.rs | 37 ++ examples/blinky_delay.rs | 46 +++ examples/flash_systick.rs | 86 +++++ examples/i2c_hal_ina260reader.rs | 291 +++++++++++++++ examples/i2c_hal_ina260serial.rs | 80 +++++ examples/i2c_hal_ssd1306alphabeter.rs | 268 ++++++++++++++ examples/led_hal_button_irq.rs | 116 ++++++ examples/serial_echo.rs | 43 +++ examples/spi_hal_apa102c.rs | 66 ++++ memory.x | 11 + openocd.cfg | 5 + openocd_program.sh | 8 + src/delay.rs | 74 ++++ src/gpio.rs | 497 ++++++++++++++++++++++++++ src/i2c.rs | 233 ++++++++++++ src/lib.rs | 32 ++ src/prelude.rs | 5 + src/rcc.rs | 174 +++++++++ src/serial.rs | 269 ++++++++++++++ src/spi.rs | 170 +++++++++ src/time.rs | 63 ++++ tools/capture_example_bloat.sh | 13 + 26 files changed, 2686 insertions(+) create mode 100644 .cargo/config create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 examples/blinky.rs create mode 100644 examples/blinky_delay.rs create mode 100644 examples/flash_systick.rs create mode 100644 examples/i2c_hal_ina260reader.rs create mode 100644 examples/i2c_hal_ina260serial.rs create mode 100644 examples/i2c_hal_ssd1306alphabeter.rs create mode 100644 examples/led_hal_button_irq.rs create mode 100644 examples/serial_echo.rs create mode 100644 examples/spi_hal_apa102c.rs create mode 100644 memory.x create mode 100644 openocd.cfg create mode 100755 openocd_program.sh create mode 100644 src/delay.rs create mode 100644 src/gpio.rs create mode 100644 src/i2c.rs create mode 100644 src/lib.rs create mode 100644 src/prelude.rs create mode 100644 src/rcc.rs create mode 100644 src/serial.rs create mode 100644 src/spi.rs create mode 100644 src/time.rs create mode 100755 tools/capture_example_bloat.sh diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..856da03 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,8 @@ +[target.thumbv6m-none-eabi] +runner = 'arm-none-eabi-gdb' +rustflags = [ + "-C", "link-arg=-Tlink.x", +] + +[build] +target = "thumbv6m-none-eabi" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d12a35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/target/ +**/*.orig +**/*.rs.bk +Cargo.lock +*.txt diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..21812c4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,53 @@ +[package] +authors = ["Daniel Egger "] +categories = [ + "embedded", + "hardware-support", + "no-std", +] +description = "Peripheral access API for STM32F0 series microcontrollers" +documentation = "https://docs.rs/stm32f0xx-hal" +keywords = [ + "arm", + "cortex-m", + "stm32f0xx", + "hal", +] +license = "0BSD" +name = "stm32f0xx-hal" +readme = "README.md" +repository = "https://github.com/stm32-rs/stm32f0xx-hal" +version = "0.7.0" + +[dependencies] +bare-metal = { version = "0.2.4", features = ["const-fn"] } +cortex-m = "0.5.8" +cortex-m-rt = "0.6.5" +nb = "0.1.1" +void = { version = "1.0.2", default-features = false } +stm32f0 = "0.4.0" + +[dependencies.cast] +default-features = false +version = "0.2.2" + +[dependencies.embedded-hal] +features = ["unproven"] +version = "0.2.2" + +[dev-dependencies] +ina260 = "0.2.3" +numtoa = "0.2.3" +panic-halt = "0.2.0" + +[features] +rt = ["stm32f0/rt"] +stm32f042 = ["stm32f0/stm32f0x2"] + +[profile.dev] +debug = true + +[profile.release] +debug = true +lto = true +opt-level = "s" diff --git a/README.md b/README.md new file mode 100644 index 0000000..aaf3066 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +stm32f0xx-hal +============= + +_stm32f0xx-hal_ contains a hardware abstraction on top of the peripheral access +API for the STMicro STM32F0xx family of microcontrollers. It replaces the +[stm32f042-hal][] by a more ubiqitous version suitable for additional families. + +Currently supported configuration are: +* stm32f042 + +The idea behind this crate is to gloss over the slight differences in the +various peripherals available on those MCUs so a HAL can be written for all +chips in that same family without having to cut and paste crates for every +single model. + +Collaboration on this crate is highly welcome as are pull requests! + +This crate relies on Adam Greigs fantastic [stm32f0][] crate to provide +appropriate register definitions and implements a partial set of the +[embedded-hal][] traits. + +Some of the implementation was shamelessly adapted from the [stm32f103xx-hal][] +crate by Jorge Aparicio. + +[stm32f0]: https://crates.io/crates/stm32f0 +[stm32f042-hal]: https://github.com/therealprof/stm32f042-hal +[stm32f103xx-hal]: https://github.com/japaric/stm32f103xx-hal +[embedded-hal]: https://github.com/japaric/embedded-hal.git + +License +------- + +[0-clause BSD license](LICENSE-0BSD.txt). diff --git a/examples/blinky.rs b/examples/blinky.rs new file mode 100644 index 0000000..fca9c2c --- /dev/null +++ b/examples/blinky.rs @@ -0,0 +1,37 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_halt; + +extern crate stm32f0xx_hal as hal; + +use hal::prelude::*; +use hal::stm32; + +use cortex_m_rt::entry; + +#[entry] +fn main() -> ! { + if let Some(p) = stm32::Peripherals::take() { + let gpioa = p.GPIOA.split(); + + /* (Re-)configure PA1 as output */ + let mut led = gpioa.pa1.into_push_pull_output(); + + loop { + /* Turn PA1 on a million times in a row */ + for _ in 0..1_000_000 { + led.set_high(); + } + /* Then turn PA1 off a million times in a row */ + for _ in 0..1_000_000 { + led.set_low(); + } + } + } + + loop { + continue; + } +} diff --git a/examples/blinky_delay.rs b/examples/blinky_delay.rs new file mode 100644 index 0000000..3d4ea28 --- /dev/null +++ b/examples/blinky_delay.rs @@ -0,0 +1,46 @@ +#![no_main] +#![no_std] + +extern crate cortex_m; +extern crate cortex_m_rt; +extern crate panic_halt; + +extern crate stm32f0xx_hal as hal; + +use hal::delay::Delay; +use hal::prelude::*; +use hal::stm32; + +use cortex_m::peripheral::Peripherals; +use cortex_m_rt::entry; + +#[entry] +fn main() -> ! { + if let (Some(p), Some(cp)) = (stm32::Peripherals::take(), Peripherals::take()) { + let gpioa = p.GPIOA.split(); + + /* (Re-)configure PA1 as output */ + let mut led = gpioa.pa1.into_push_pull_output(); + + /* Constrain clocking registers */ + let mut rcc = p.RCC.constrain(); + + /* Configure clock to 8 MHz (i.e. the default) and freeze it */ + let clocks = rcc.cfgr.sysclk(8.mhz()).freeze(); + + /* Get delay provider */ + let mut delay = Delay::new(cp.SYST, clocks); + + loop { + led.set_high(); + delay.delay_ms(1_000_u16); + + led.set_low(); + delay.delay_ms(1_000_u16); + } + } + + loop { + continue; + } +} diff --git a/examples/flash_systick.rs b/examples/flash_systick.rs new file mode 100644 index 0000000..232b041 --- /dev/null +++ b/examples/flash_systick.rs @@ -0,0 +1,86 @@ +#![no_main] +#![no_std] + +extern crate cortex_m; +extern crate cortex_m_rt; +extern crate panic_halt; + +extern crate stm32f0xx_hal as hal; + +use hal::gpio::*; +use hal::prelude::*; +use hal::stm32; + +use cortex_m::interrupt::Mutex; +use cortex_m::peripheral::syst::SystClkSource::Core; +use cortex_m::peripheral::Peripherals; +use cortex_m_rt::{entry, exception}; + +use core::cell::RefCell; +use core::ops::DerefMut; + +static GPIO: Mutex>>>> = Mutex::new(RefCell::new(None)); + +#[entry] +fn main() -> ! { + if let (Some(p), Some(cp)) = (stm32::Peripherals::take(), Peripherals::take()) { + let gpioa = p.GPIOA.split(); + let mut rcc = p.RCC.constrain(); + let _ = rcc.cfgr.sysclk(48.mhz()).freeze(); + let mut syst = cp.SYST; + + /* (Re-)configure PA1 as output */ + let mut led = gpioa.pa1.into_push_pull_output(); + + cortex_m::interrupt::free(move |cs| { + *GPIO.borrow(cs).borrow_mut() = Some(led); + }); + + /* Initialise SysTick counter with a defined value */ + unsafe { syst.cvr.write(1) }; + + /* Set source for SysTick counter, here full operating frequency (== 8MHz) */ + syst.set_clock_source(Core); + + /* Set reload value, i.e. timer delay 48 MHz/4 Mcounts == 12Hz or 83ms */ + syst.set_reload(4_000_000 - 1); + + /* Start counter */ + syst.enable_counter(); + + /* Start interrupt generation */ + syst.enable_interrupt(); + } + + loop { + continue; + } +} + +/* Define an exception, i.e. function to call when exception occurs. Here if our SysTick timer + * trips the flash function will be called and the specified stated passed in via argument */ +//, flash, state: u8 = 1); +#[exception] +fn SysTick() -> ! { + static mut state: u8 = 1; + + /* Enter critical section */ + cortex_m::interrupt::free(|cs| { + if let Some(ref mut led) = *GPIO.borrow(cs).borrow_mut().deref_mut() { + /* Check state variable, keep LED off most of the time and turn it on every 10th tick */ + if *state < 10 { + /* If set turn off the LED */ + led.set_low(); + + /* And now increment state variable */ + *state += 1; + } else { + /* If not set, turn on the LED */ + led.set_high(); + + /* And set new state variable back to 0 */ + *state = 0; + } + } + }); +} diff --git a/examples/i2c_hal_ina260reader.rs b/examples/i2c_hal_ina260reader.rs new file mode 100644 index 0000000..ed24c46 --- /dev/null +++ b/examples/i2c_hal_ina260reader.rs @@ -0,0 +1,291 @@ +#![no_main] +#![no_std] + +extern crate cortex_m; +extern crate cortex_m_rt; +extern crate embedded_hal; +extern crate panic_halt; + +extern crate stm32f0xx_hal as hal; + +extern crate numtoa; + +use hal::i2c::*; +use hal::prelude::*; +use hal::stm32; + +use embedded_hal::blocking::i2c::Write; + +use numtoa::NumToA; + +use cortex_m_rt::entry; + +const SSD1306_BYTE_CMD: u8 = 0x00; +const SSD1306_BYTE_DATA: u8 = 0x40; +const SSD1306_BYTE_CMD_SINGLE: u8 = 0x80; + +const SSD1306_DISPLAY_RAM: u8 = 0xA4; +const SSD1306_DISPLAY_NORMAL: u8 = 0xA6; +const SSD1306_DISPLAY_OFF: u8 = 0xAE; +const SSD1306_DISPLAY_ON: u8 = 0xAF; + +const SSD1306_MEMORY_ADDR_MODE: u8 = 0x20; +const SSD1306_COLUMN_RANGE: u8 = 0x21; +const SSD1306_PAGE_RANGE: u8 = 0x22; + +const SSD1306_DISPLAY_START_LINE: u8 = 0x40; +const SSD1306_SCAN_MODE_NORMAL: u8 = 0xC0; +const SSD1306_DISPLAY_OFFSET: u8 = 0xD3; +const SSD1306_PIN_MAP: u8 = 0xDA; + +const SSD1306_DISPLAY_CLK_DIV: u8 = 0xD5; +const SSD1306_CHARGE_PUMP: u8 = 0x8D; + +#[entry] +fn main() -> ! { + if let Some(p) = stm32::Peripherals::take() { + let gpiof = p.GPIOF.split(); + let mut rcc = p.RCC.constrain(); + let _ = rcc.cfgr.freeze(); + + let scl = gpiof + .pf1 + .into_alternate_af1() + .internal_pull_up(true) + .set_open_drain(); + let sda = gpiof + .pf0 + .into_alternate_af1() + .internal_pull_up(true) + .set_open_drain(); + + /* Setup I2C1 */ + let mut i2c = I2c::i2c1(p.I2C1, (scl, sda), 10.khz()); + + /* Initialise SSD1306 display */ + let _ = ssd1306_init(&mut i2c); + + /* Print a welcome message on the display */ + let _ = ssd1306_pos(&mut i2c, 0, 0); + + /* Endless loop */ + loop { + let _ = ssd1306_pos(&mut i2c, 0, 0); + let mut data = [0; 2]; + let _ = i2c.write_read(0x40, &[0x00], &mut data); + let config = (u16::from(data[0]) << 8) | u16::from(data[1]); + + let mut buffer = [0u8; 10]; + let _ = ssd1306_print_bytes(&mut i2c, config.numtoa(10, &mut buffer)); + let _ = ssd1306_pos(&mut i2c, 0, 1); + + let mut data = [0; 2]; + let _ = i2c.write_read(0x40, &[0x02], &mut data); + let mut voltage = ((u32::from(data[0]) << 8) | u32::from(data[1])) * 1250; + + let _ = ssd1306_print_bytes(&mut i2c, voltage.numtoa(10, &mut buffer)); + let _ = ssd1306_print_bytes(&mut i2c, b"uV "); + + let _ = ssd1306_pos(&mut i2c, 0, 2); + + let mut data = [0; 2]; + let _ = i2c.write_read(0x40, &[0x01], &mut data); + voltage = ((u32::from(data[0]) << 8) | u32::from(data[1])) * 1250; + + let _ = ssd1306_print_bytes(&mut i2c, voltage.numtoa(10, &mut buffer)); + let _ = ssd1306_print_bytes(&mut i2c, b"uA "); + } + } + + loop { + continue; + } +} + +/// Print characters on the display with the embedded 7x7 font +fn ssd1306_print_bytes(i2c: &mut I2C, bytes: &[u8]) -> Result<(), E> +where + I2C: Write, +{ + /* A 7x7 font shamelessly borrowed from https://github.com/techninja/MarioChron/ */ + const FONT_7X7: [u8; 672] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // (space) + 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x00, // ! + 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, // " + 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00, 0x00, // # + 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00, 0x00, // $ + 0x23, 0x13, 0x08, 0x64, 0x62, 0x00, 0x00, // % + 0x36, 0x49, 0x55, 0x22, 0x50, 0x00, 0x00, // & + 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, 0x00, // ' + 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, 0x00, // ( + 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00, 0x00, // ) + 0x08, 0x2A, 0x1C, 0x2A, 0x08, 0x00, 0x00, // * + 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x00, // + + 0x00, 0x50, 0x30, 0x00, 0x00, 0x00, 0x00, // , + 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // - + 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, // . + 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00, // / + 0x1C, 0x3E, 0x61, 0x41, 0x43, 0x3E, 0x1C, // 0 + 0x40, 0x42, 0x7F, 0x7F, 0x40, 0x40, 0x00, // 1 + 0x62, 0x73, 0x79, 0x59, 0x5D, 0x4F, 0x46, // 2 + 0x20, 0x61, 0x49, 0x4D, 0x4F, 0x7B, 0x31, // 3 + 0x18, 0x1C, 0x16, 0x13, 0x7F, 0x7F, 0x10, // 4 + 0x27, 0x67, 0x45, 0x45, 0x45, 0x7D, 0x38, // 5 + 0x3C, 0x7E, 0x4B, 0x49, 0x49, 0x79, 0x30, // 6 + 0x03, 0x03, 0x71, 0x79, 0x0D, 0x07, 0x03, // 7 + 0x36, 0x7F, 0x49, 0x49, 0x49, 0x7F, 0x36, // 8 + 0x06, 0x4F, 0x49, 0x49, 0x69, 0x3F, 0x1E, // 9 + 0x00, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, // : + 0x00, 0x56, 0x36, 0x00, 0x00, 0x00, 0x00, // ; + 0x00, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00, // < + 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x00, // = + 0x41, 0x22, 0x14, 0x08, 0x00, 0x00, 0x00, // > + 0x02, 0x01, 0x51, 0x09, 0x06, 0x00, 0x00, // ? + 0x32, 0x49, 0x79, 0x41, 0x3E, 0x00, 0x00, // @ + 0x7E, 0x11, 0x11, 0x11, 0x7E, 0x00, 0x00, // A + 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00, // B + 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00, 0x00, // C + 0x7F, 0x7F, 0x41, 0x41, 0x63, 0x3E, 0x1C, // D + 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00, 0x00, // E + 0x7F, 0x09, 0x09, 0x01, 0x01, 0x00, 0x00, // F + 0x3E, 0x41, 0x41, 0x51, 0x32, 0x00, 0x00, // G + 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x00, // H + 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, 0x00, // I + 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, 0x00, // J + 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00, // K + 0x7F, 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, // L + 0x7F, 0x02, 0x04, 0x02, 0x7F, 0x00, 0x00, // M + 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00, 0x00, // N + 0x3E, 0x7F, 0x41, 0x41, 0x41, 0x7F, 0x3E, // O + 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00, 0x00, // P + 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, 0x00, // Q + 0x7F, 0x7F, 0x11, 0x31, 0x79, 0x6F, 0x4E, // R + 0x46, 0x49, 0x49, 0x49, 0x31, 0x00, 0x00, // S + 0x01, 0x01, 0x7F, 0x01, 0x01, 0x00, 0x00, // T + 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00, 0x00, // U + 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00, 0x00, // V + 0x7F, 0x7F, 0x38, 0x1C, 0x38, 0x7F, 0x7F, // W + 0x63, 0x14, 0x08, 0x14, 0x63, 0x00, 0x00, // X + 0x03, 0x04, 0x78, 0x04, 0x03, 0x00, 0x00, // Y + 0x61, 0x51, 0x49, 0x45, 0x43, 0x00, 0x00, // Z + 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00, 0x00, // [ + 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, // "\" + 0x41, 0x41, 0x7F, 0x00, 0x00, 0x00, 0x00, // ] + 0x04, 0x02, 0x01, 0x02, 0x04, 0x00, 0x00, // ^ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, // _ + 0x00, 0x01, 0x02, 0x04, 0x00, 0x00, 0x00, // ` + 0x20, 0x54, 0x54, 0x54, 0x78, 0x00, 0x00, // a + 0x7F, 0x48, 0x44, 0x44, 0x38, 0x00, 0x00, // b + 0x38, 0x44, 0x44, 0x44, 0x20, 0x00, 0x00, // c + 0x38, 0x44, 0x44, 0x48, 0x7F, 0x00, 0x00, // d + 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, 0x00, // e + 0x08, 0x7E, 0x09, 0x01, 0x02, 0x00, 0x00, // f + 0x08, 0x14, 0x54, 0x54, 0x3C, 0x00, 0x00, // g + 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, 0x00, // h + 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00, 0x00, // i + 0x20, 0x40, 0x44, 0x3D, 0x00, 0x00, 0x00, // j + 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00, // k + 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, 0x00, // l + 0x7C, 0x04, 0x18, 0x04, 0x78, 0x00, 0x00, // m + 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00, 0x00, // n + 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, // o + 0x7C, 0x14, 0x14, 0x14, 0x08, 0x00, 0x00, // p + 0x08, 0x14, 0x14, 0x18, 0x7C, 0x00, 0x00, // q + 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00, 0x00, // r + 0x48, 0x54, 0x54, 0x54, 0x20, 0x00, 0x00, // s + 0x04, 0x3F, 0x44, 0x40, 0x20, 0x00, 0x00, // t + 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00, 0x00, // u + 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00, 0x00, // v + 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00, 0x00, // w + 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, // x + 0x0C, 0x50, 0x50, 0x50, 0x3C, 0x00, 0x00, // y + 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, 0x00, // z + 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00, // { + 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, // | + 0x00, 0x41, 0x36, 0x08, 0x00, 0x00, 0x00, // } + 0x08, 0x08, 0x2A, 0x1C, 0x08, 0x00, 0x00, // -> + 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x00, 0x00, // <- + ]; + + for c in bytes { + /* Create an array with our I2C instruction and a blank column at the end */ + let mut data: [u8; 9] = [SSD1306_BYTE_DATA, 0, 0, 0, 0, 0, 0, 0, 0]; + + /* Calculate our index into the character table above */ + let index = (*c as usize - 0x20) * 7; + + /* Populate the middle of the array with the data from the character array at the right + * index */ + data[1..8].copy_from_slice(&FONT_7X7[index..index + 7]); + + /* Write it out to the I2C bus */ + i2c.write(0x3C, &data)? + } + + Ok(()) +} + +/// Initialise display with some useful values +fn ssd1306_init(i2c: &mut I2C) -> Result<(), E> +where + I2C: Write, +{ + i2c.write(0x3C, &[SSD1306_BYTE_CMD_SINGLE, SSD1306_DISPLAY_OFF])?; + i2c.write( + 0x3C, + &[SSD1306_BYTE_CMD_SINGLE, SSD1306_DISPLAY_CLK_DIV, 0x80], + )?; + i2c.write(0x3C, &[SSD1306_BYTE_CMD_SINGLE, SSD1306_SCAN_MODE_NORMAL])?; + i2c.write( + 0x3C, + &[SSD1306_BYTE_CMD_SINGLE, SSD1306_DISPLAY_OFFSET, 0x00, 0x00], + )?; + i2c.write( + 0x3C, + &[SSD1306_BYTE_CMD_SINGLE, SSD1306_MEMORY_ADDR_MODE, 0x00], + )?; + i2c.write( + 0x3C, + &[SSD1306_BYTE_CMD_SINGLE, SSD1306_DISPLAY_START_LINE, 0x00], + )?; + i2c.write(0x3C, &[SSD1306_BYTE_CMD_SINGLE, SSD1306_CHARGE_PUMP, 0x14])?; + i2c.write(0x3C, &[SSD1306_BYTE_CMD_SINGLE, SSD1306_PIN_MAP, 0x12])?; + i2c.write(0x3C, &[SSD1306_BYTE_CMD_SINGLE, SSD1306_DISPLAY_RAM])?; + i2c.write(0x3C, &[SSD1306_BYTE_CMD_SINGLE, SSD1306_DISPLAY_NORMAL])?; + i2c.write(0x3C, &[SSD1306_BYTE_CMD_SINGLE, SSD1306_DISPLAY_ON])?; + + let data = [ + SSD1306_BYTE_DATA, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]; + + for _ in 0..128 { + i2c.write(0x3C, &data)?; + } + + Ok(()) +} + +/// Position cursor at specified x, y block coordinate (multiple of 8) +fn ssd1306_pos(i2c: &mut I2C, x: u8, y: u8) -> Result<(), E> +where + I2C: Write, +{ + let data = [ + SSD1306_BYTE_CMD, + SSD1306_COLUMN_RANGE, + x * 8, + 0x7F, + SSD1306_PAGE_RANGE, + y, + 0x07, + ]; + i2c.write(0x3C, &data) +} diff --git a/examples/i2c_hal_ina260serial.rs b/examples/i2c_hal_ina260serial.rs new file mode 100644 index 0000000..ba70b17 --- /dev/null +++ b/examples/i2c_hal_ina260serial.rs @@ -0,0 +1,80 @@ +#![no_main] +#![no_std] + +extern crate cortex_m; +extern crate cortex_m_rt; +extern crate embedded_hal; +extern crate panic_halt; + +extern crate stm32f0xx_hal as hal; + +extern crate ina260; +extern crate numtoa; + +use hal::i2c::*; +use hal::prelude::*; +use hal::serial::*; +use hal::stm32; + +use numtoa::NumToA; + +use ina260::*; + +use core::fmt::Write; +use cortex_m_rt::entry; + +#[entry] +fn main() -> ! { + if let Some(p) = stm32::Peripherals::take() { + let gpiof = p.GPIOF.split(); + let gpioa = p.GPIOA.split(); + let mut clocks = p.RCC.constrain().cfgr.freeze(); + + /* Initialise serial pins */ + let tx = gpioa.pa9.into_alternate_af1(); + let rx = gpioa.pa10.into_alternate_af1(); + + /* Setup serial port */ + let serial = Serial::usart1(p.USART1, (tx, rx), 115_200.bps(), clocks); + let (mut tx, mut _rx) = serial.split(); + + /* Initialise I2C pins */ + let scl = gpiof + .pf1 + .into_alternate_af1() + .internal_pull_up(true) + .set_open_drain(); + let sda = gpiof + .pf0 + .into_alternate_af1() + .internal_pull_up(true) + .set_open_drain(); + + /* Setup I2C1 */ + let mut i2c = I2c::i2c1(p.I2C1, (scl, sda), 1.khz()); + let mut ina260 = INA260::new(i2c, 0x40).unwrap(); + + /* Endless loop */ + loop { + let mut buffer = [0u8; 10]; + + /* Read and print voltage */ + let voltage = ina260.voltage().unwrap(); + let _ = tx.write_str(unsafe { + core::str::from_utf8_unchecked(voltage.numtoa(10, &mut buffer)) + }); + let _ = tx.write_str("uV\n\r"); + + /* Read and print current */ + let current = ina260.current().unwrap(); + let _ = tx.write_str(unsafe { + core::str::from_utf8_unchecked(current.numtoa(10, &mut buffer)) + }); + let _ = tx.write_str("uA\n\r"); + } + } + + loop { + continue; + } +} diff --git a/examples/i2c_hal_ssd1306alphabeter.rs b/examples/i2c_hal_ssd1306alphabeter.rs new file mode 100644 index 0000000..d707ffc --- /dev/null +++ b/examples/i2c_hal_ssd1306alphabeter.rs @@ -0,0 +1,268 @@ +#![no_main] +#![no_std] + +extern crate cortex_m; +extern crate cortex_m_rt; +extern crate embedded_hal; +extern crate panic_halt; + +extern crate stm32f0xx_hal as hal; + +use hal::i2c::*; +use hal::prelude::*; +use hal::stm32; + +use cortex_m_rt::entry; +use embedded_hal::blocking::i2c::Write; + +const SSD1306_BYTE_CMD: u8 = 0x00; +const SSD1306_BYTE_DATA: u8 = 0x40; +const SSD1306_BYTE_CMD_SINGLE: u8 = 0x80; + +const SSD1306_DISPLAY_RAM: u8 = 0xA4; +const SSD1306_DISPLAY_NORMAL: u8 = 0xA6; +const SSD1306_DISPLAY_OFF: u8 = 0xAE; +const SSD1306_DISPLAY_ON: u8 = 0xAF; + +const SSD1306_MEMORY_ADDR_MODE: u8 = 0x20; +const SSD1306_COLUMN_RANGE: u8 = 0x21; +const SSD1306_PAGE_RANGE: u8 = 0x22; + +const SSD1306_DISPLAY_START_LINE: u8 = 0x40; +const SSD1306_SCAN_MODE_NORMAL: u8 = 0xC0; +const SSD1306_DISPLAY_OFFSET: u8 = 0xD3; +const SSD1306_PIN_MAP: u8 = 0xDA; + +const SSD1306_DISPLAY_CLK_DIV: u8 = 0xD5; +const SSD1306_CHARGE_PUMP: u8 = 0x8D; + +#[entry] +fn main() -> ! { + if let Some(p) = stm32::Peripherals::take() { + let gpiof = p.GPIOF.split(); + let mut rcc = p.RCC.constrain(); + let _ = rcc.cfgr.freeze(); + + let scl = gpiof + .pf1 + .into_alternate_af1() + .internal_pull_up(true) + .set_open_drain(); + let sda = gpiof + .pf0 + .into_alternate_af1() + .internal_pull_up(true) + .set_open_drain(); + + /* Setup I2C1 */ + let mut i2c = I2c::i2c1(p.I2C1, (scl, sda), 400.khz()); + + /* Initialise SSD1306 display */ + let _ = ssd1306_init(&mut i2c); + + /* Print a welcome message on the display */ + let _ = ssd1306_pos(&mut i2c, 0, 0); + + /* Endless loop */ + loop { + for c in 97..123 { + let _ = ssd1306_print_bytes(&mut i2c, &[c]); + } + for c in 65..91 { + let _ = ssd1306_print_bytes(&mut i2c, &[c]); + } + } + } + + loop { + continue; + } +} + +/// Print characters on the display with the embedded 7x7 font +fn ssd1306_print_bytes(i2c: &mut I2C, bytes: &[u8]) -> Result<(), E> +where + I2C: Write, +{ + /* A 7x7 font shamelessly borrowed from https://github.com/techninja/MarioChron/ */ + const FONT_7X7: [u8; 672] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // (space) + 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x00, // ! + 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, // " + 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00, 0x00, // # + 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00, 0x00, // $ + 0x23, 0x13, 0x08, 0x64, 0x62, 0x00, 0x00, // % + 0x36, 0x49, 0x55, 0x22, 0x50, 0x00, 0x00, // & + 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, 0x00, // ' + 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, 0x00, // ( + 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00, 0x00, // ) + 0x08, 0x2A, 0x1C, 0x2A, 0x08, 0x00, 0x00, // * + 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x00, // + + 0x00, 0x50, 0x30, 0x00, 0x00, 0x00, 0x00, // , + 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // - + 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, // . + 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00, // / + 0x1C, 0x3E, 0x61, 0x41, 0x43, 0x3E, 0x1C, // 0 + 0x40, 0x42, 0x7F, 0x7F, 0x40, 0x40, 0x00, // 1 + 0x62, 0x73, 0x79, 0x59, 0x5D, 0x4F, 0x46, // 2 + 0x20, 0x61, 0x49, 0x4D, 0x4F, 0x7B, 0x31, // 3 + 0x18, 0x1C, 0x16, 0x13, 0x7F, 0x7F, 0x10, // 4 + 0x27, 0x67, 0x45, 0x45, 0x45, 0x7D, 0x38, // 5 + 0x3C, 0x7E, 0x4B, 0x49, 0x49, 0x79, 0x30, // 6 + 0x03, 0x03, 0x71, 0x79, 0x0D, 0x07, 0x03, // 7 + 0x36, 0x7F, 0x49, 0x49, 0x49, 0x7F, 0x36, // 8 + 0x06, 0x4F, 0x49, 0x49, 0x69, 0x3F, 0x1E, // 9 + 0x00, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, // : + 0x00, 0x56, 0x36, 0x00, 0x00, 0x00, 0x00, // ; + 0x00, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00, // < + 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x00, // = + 0x41, 0x22, 0x14, 0x08, 0x00, 0x00, 0x00, // > + 0x02, 0x01, 0x51, 0x09, 0x06, 0x00, 0x00, // ? + 0x32, 0x49, 0x79, 0x41, 0x3E, 0x00, 0x00, // @ + 0x7E, 0x11, 0x11, 0x11, 0x7E, 0x00, 0x00, // A + 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00, // B + 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00, 0x00, // C + 0x7F, 0x7F, 0x41, 0x41, 0x63, 0x3E, 0x1C, // D + 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00, 0x00, // E + 0x7F, 0x09, 0x09, 0x01, 0x01, 0x00, 0x00, // F + 0x3E, 0x41, 0x41, 0x51, 0x32, 0x00, 0x00, // G + 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x00, // H + 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, 0x00, // I + 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, 0x00, // J + 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00, // K + 0x7F, 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, // L + 0x7F, 0x02, 0x04, 0x02, 0x7F, 0x00, 0x00, // M + 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00, 0x00, // N + 0x3E, 0x7F, 0x41, 0x41, 0x41, 0x7F, 0x3E, // O + 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00, 0x00, // P + 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, 0x00, // Q + 0x7F, 0x7F, 0x11, 0x31, 0x79, 0x6F, 0x4E, // R + 0x46, 0x49, 0x49, 0x49, 0x31, 0x00, 0x00, // S + 0x01, 0x01, 0x7F, 0x01, 0x01, 0x00, 0x00, // T + 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00, 0x00, // U + 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00, 0x00, // V + 0x7F, 0x7F, 0x38, 0x1C, 0x38, 0x7F, 0x7F, // W + 0x63, 0x14, 0x08, 0x14, 0x63, 0x00, 0x00, // X + 0x03, 0x04, 0x78, 0x04, 0x03, 0x00, 0x00, // Y + 0x61, 0x51, 0x49, 0x45, 0x43, 0x00, 0x00, // Z + 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00, 0x00, // [ + 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, // "\" + 0x41, 0x41, 0x7F, 0x00, 0x00, 0x00, 0x00, // ] + 0x04, 0x02, 0x01, 0x02, 0x04, 0x00, 0x00, // ^ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, // _ + 0x00, 0x01, 0x02, 0x04, 0x00, 0x00, 0x00, // ` + 0x20, 0x54, 0x54, 0x54, 0x78, 0x00, 0x00, // a + 0x7F, 0x48, 0x44, 0x44, 0x38, 0x00, 0x00, // b + 0x38, 0x44, 0x44, 0x44, 0x20, 0x00, 0x00, // c + 0x38, 0x44, 0x44, 0x48, 0x7F, 0x00, 0x00, // d + 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, 0x00, // e + 0x08, 0x7E, 0x09, 0x01, 0x02, 0x00, 0x00, // f + 0x08, 0x14, 0x54, 0x54, 0x3C, 0x00, 0x00, // g + 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, 0x00, // h + 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00, 0x00, // i + 0x20, 0x40, 0x44, 0x3D, 0x00, 0x00, 0x00, // j + 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00, // k + 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, 0x00, // l + 0x7C, 0x04, 0x18, 0x04, 0x78, 0x00, 0x00, // m + 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00, 0x00, // n + 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, // o + 0x7C, 0x14, 0x14, 0x14, 0x08, 0x00, 0x00, // p + 0x08, 0x14, 0x14, 0x18, 0x7C, 0x00, 0x00, // q + 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00, 0x00, // r + 0x48, 0x54, 0x54, 0x54, 0x20, 0x00, 0x00, // s + 0x04, 0x3F, 0x44, 0x40, 0x20, 0x00, 0x00, // t + 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00, 0x00, // u + 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00, 0x00, // v + 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00, 0x00, // w + 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, // x + 0x0C, 0x50, 0x50, 0x50, 0x3C, 0x00, 0x00, // y + 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, 0x00, // z + 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00, // { + 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, // | + 0x00, 0x41, 0x36, 0x08, 0x00, 0x00, 0x00, // } + 0x08, 0x08, 0x2A, 0x1C, 0x08, 0x00, 0x00, // -> + 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x00, 0x00, // <- + ]; + + for c in bytes { + /* Create an array with our I2C instruction and a blank column at the end */ + let mut data: [u8; 9] = [SSD1306_BYTE_DATA, 0, 0, 0, 0, 0, 0, 0, 0]; + + /* Calculate our index into the character table above */ + let index = (*c as usize - 0x20) * 7; + + /* Populate the middle of the array with the data from the character array at the right + * index */ + data[1..8].copy_from_slice(&FONT_7X7[index..index + 7]); + + /* Write it out to the I2C bus */ + i2c.write(0x3C, &data)? + } + + Ok(()) +} + +/// Initialise display with some useful values +fn ssd1306_init(i2c: &mut I2C) -> Result<(), E> +where + I2C: Write, +{ + i2c.write(0x3C, &[SSD1306_BYTE_CMD_SINGLE, SSD1306_DISPLAY_OFF])?; + i2c.write( + 0x3C, + &[SSD1306_BYTE_CMD_SINGLE, SSD1306_DISPLAY_CLK_DIV, 0x80], + )?; + i2c.write(0x3C, &[SSD1306_BYTE_CMD_SINGLE, SSD1306_SCAN_MODE_NORMAL])?; + i2c.write( + 0x3C, + &[SSD1306_BYTE_CMD_SINGLE, SSD1306_DISPLAY_OFFSET, 0x00, 0x00], + )?; + i2c.write( + 0x3C, + &[SSD1306_BYTE_CMD_SINGLE, SSD1306_MEMORY_ADDR_MODE, 0x00], + )?; + i2c.write( + 0x3C, + &[SSD1306_BYTE_CMD_SINGLE, SSD1306_DISPLAY_START_LINE, 0x00], + )?; + i2c.write(0x3C, &[SSD1306_BYTE_CMD_SINGLE, SSD1306_CHARGE_PUMP, 0x14])?; + i2c.write(0x3C, &[SSD1306_BYTE_CMD_SINGLE, SSD1306_PIN_MAP, 0x12])?; + i2c.write(0x3C, &[SSD1306_BYTE_CMD_SINGLE, SSD1306_DISPLAY_RAM])?; + i2c.write(0x3C, &[SSD1306_BYTE_CMD_SINGLE, SSD1306_DISPLAY_NORMAL])?; + i2c.write(0x3C, &[SSD1306_BYTE_CMD_SINGLE, SSD1306_DISPLAY_ON])?; + + let data = [ + SSD1306_BYTE_DATA, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]; + + for _ in 0..128 { + i2c.write(0x3C, &data)?; + } + + Ok(()) +} + +/// Position cursor at specified x, y block coordinate (multiple of 8) +fn ssd1306_pos(i2c: &mut I2C, x: u8, y: u8) -> Result<(), E> +where + I2C: Write, +{ + let data = [ + SSD1306_BYTE_CMD, + SSD1306_COLUMN_RANGE, + x * 8, + 0x7F, + SSD1306_PAGE_RANGE, + y, + 0x07, + ]; + i2c.write(0x3C, &data) +} diff --git a/examples/led_hal_button_irq.rs b/examples/led_hal_button_irq.rs new file mode 100644 index 0000000..021484d --- /dev/null +++ b/examples/led_hal_button_irq.rs @@ -0,0 +1,116 @@ +#![no_main] +#![no_std] + +extern crate cortex_m; +extern crate cortex_m_rt; +extern crate panic_halt; + +#[macro_use] +extern crate stm32f0xx_hal as hal; + +use hal::delay::Delay; +use hal::gpio::*; +use hal::prelude::*; + +use cortex_m::interrupt::Mutex; +use cortex_m::peripheral::Peripherals as c_m_Peripherals; +use cortex_m_rt::entry; + +pub use hal::stm32; +pub use hal::stm32::*; + +use core::cell::RefCell; +use core::ops::DerefMut; + +// Make our LED globally available +static LED: Mutex>>>> = Mutex::new(RefCell::new(None)); + +// Make our delay provider globally available +static DELAY: Mutex>> = Mutex::new(RefCell::new(None)); + +// Make external interrupt registers globally available +static INT: Mutex>> = Mutex::new(RefCell::new(None)); + +#[entry] +fn main() -> ! { + if let (Some(p), Some(cp)) = (Peripherals::take(), c_m_Peripherals::take()) { + let gpiob = p.GPIOB.split(); + let syscfg = p.SYSCFG_COMP; + let exti = p.EXTI; + + // Enable clock for SYSCFG + let mut rcc = p.RCC; + rcc.apb2enr.modify(|_, w| w.syscfgen().set_bit()); + + // Configure PB1 as input (button) + let _ = gpiob.pb1.into_pull_down_input(); + + // Configure PB3 as output (LED) + let mut led = gpiob.pb3.into_push_pull_output(); + + // Turn off LED + led.set_low(); + + // Configure clock to 8 MHz (i.e. the default) and freeze it + let clocks = rcc.constrain().cfgr.sysclk(8.mhz()).freeze(); + + // Initialise delay provider + let mut delay = Delay::new(cp.SYST, clocks); + + // Enable external interrupt for PB1 + syscfg + .syscfg_exticr1 + .modify(|_, w| unsafe { w.exti1().bits(1) }); + + // Set interrupt request mask for line 1 + exti.imr.modify(|_, w| w.mr1().set_bit()); + + // Set interrupt rising trigger for line 1 + exti.rtsr.modify(|_, w| w.tr1().set_bit()); + + // Move control over LED and DELAY and EXTI into global mutexes + cortex_m::interrupt::free(move |cs| { + *LED.borrow(cs).borrow_mut() = Some(led); + *DELAY.borrow(cs).borrow_mut() = Some(delay); + *INT.borrow(cs).borrow_mut() = Some(exti); + }); + + // Enable EXTI IRQ, set prio 1 and clear any pending IRQs + let mut nvic = cp.NVIC; + nvic.enable(Interrupt::EXTI0_1); + unsafe { nvic.set_priority(Interrupt::EXTI0_1, 1) }; + cortex_m::peripheral::NVIC::unpend(Interrupt::EXTI0_1); + } + + loop { + continue; + } +} + +/* Define an intterupt handler, i.e. function to call when exception occurs. Here if our external + * interrupt trips the flash function which will be called */ +interrupt!(EXTI0_1, button_press); + +fn button_press() { + // Enter critical section + cortex_m::interrupt::free(|cs| { + // Obtain all Mutex protected resources + if let (&mut Some(ref mut led), &mut Some(ref mut delay), &mut Some(ref mut exti)) = ( + LED.borrow(cs).borrow_mut().deref_mut(), + DELAY.borrow(cs).borrow_mut().deref_mut(), + INT.borrow(cs).borrow_mut().deref_mut(), + ) { + // Turn on LED + led.set_high(); + + // Wait a second + delay.delay_ms(1_000_u16); + + // Turn off LED + led.set_low(); + + // Clear interrupt + exti.pr.modify(|_, w| w.pif1().set_bit()); + } + }); +} diff --git a/examples/serial_echo.rs b/examples/serial_echo.rs new file mode 100644 index 0000000..72f81a7 --- /dev/null +++ b/examples/serial_echo.rs @@ -0,0 +1,43 @@ +#![no_main] +#![no_std] + +extern crate cortex_m; +extern crate cortex_m_rt; +extern crate panic_halt; + +extern crate stm32f0xx_hal as hal; + +use hal::prelude::*; +use hal::stm32; + +#[macro_use(block)] +extern crate nb; + +use hal::serial::Serial; + +use cortex_m_rt::entry; + +#[entry] +fn main() -> ! { + if let Some(p) = stm32::Peripherals::take() { + let gpioa = p.GPIOA.split(); + let mut rcc = p.RCC.constrain(); + let clocks = rcc.cfgr.sysclk(48.mhz()).freeze(); + + let tx = gpioa.pa9.into_alternate_af1(); + let rx = gpioa.pa10.into_alternate_af1(); + + let serial = Serial::usart1(p.USART1, (tx, rx), 115_200.bps(), clocks); + + let (mut tx, mut rx) = serial.split(); + + loop { + let received = block!(rx.read()).unwrap(); + block!(tx.write(received)).ok(); + } + } + + loop { + continue; + } +} diff --git a/examples/spi_hal_apa102c.rs b/examples/spi_hal_apa102c.rs new file mode 100644 index 0000000..bc62a70 --- /dev/null +++ b/examples/spi_hal_apa102c.rs @@ -0,0 +1,66 @@ +#![no_main] +#![no_std] + +extern crate cortex_m; +extern crate cortex_m_rt; +extern crate panic_halt; + +extern crate stm32f0xx_hal as hal; + +use hal::prelude::*; +use hal::spi::Spi; +use hal::spi::{Mode, Phase, Polarity}; +use hal::stm32; + +use cortex_m_rt::entry; + +#[entry] +fn main() -> ! { + pub const MODE: Mode = Mode { + polarity: Polarity::IdleHigh, + phase: Phase::CaptureOnSecondTransition, + }; + + if let Some(p) = stm32::Peripherals::take() { + let mut rcc = p.RCC.constrain(); + let clocks = rcc.cfgr.freeze(); + let mut gpioa = p.GPIOA.split(); + + // Configure pins for SPI + let sck = gpioa.pa5.into_alternate_af0(); + let miso = gpioa.pa6.into_alternate_af0(); + let mosi = gpioa.pa7.into_alternate_af0(); + + // Configure SPI with 100kHz rate + let mut spi = Spi::spi1(p.SPI1, (sck, miso, mosi), MODE, 100_000.hz(), clocks); + + // Cycle through colors on 16 chained APA102C LEDs + loop { + for r in 0..255 { + let _ = spi.write(&[0, 0, 0, 0]); + for _i in 0..16 { + let _ = spi.write(&[0b1110_0001, 0, 0, r]); + } + let _ = spi.write(&[0xFF, 0xFF, 0xFF, 0xFF]); + } + for b in 0..255 { + let _ = spi.write(&[0, 0, 0, 0]); + for _i in 0..16 { + let _ = spi.write(&[0b1110_0001, b, 0, 0]); + } + let _ = spi.write(&[0xFF, 0xFF, 0xFF, 0xFF]); + } + for g in 0..255 { + let _ = spi.write(&[0, 0, 0, 0]); + for _i in 0..16 { + let _ = spi.write(&[0b1110_0001, 0, g, 0]); + } + let _ = spi.write(&[0xFF, 0xFF, 0xFF, 0xFF]); + } + } + } + + loop { + continue; + } +} diff --git a/memory.x b/memory.x new file mode 100644 index 0000000..51b2492 --- /dev/null +++ b/memory.x @@ -0,0 +1,11 @@ +MEMORY +{ + /* NOTE K = KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x08000000, LENGTH = 32K + RAM : ORIGIN = 0x20000000, LENGTH = 6K +} + +/* This is where the call stack will be allocated. */ +/* The stack is of the full descending type. */ +/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */ +_stack_start = ORIGIN(RAM) + LENGTH(RAM); diff --git a/openocd.cfg b/openocd.cfg new file mode 100644 index 0000000..755e7c1 --- /dev/null +++ b/openocd.cfg @@ -0,0 +1,5 @@ +source [find interface/cmsis-dap.cfg] +source [find target/stm32f0x.cfg] + +init +flash probe 0 diff --git a/openocd_program.sh b/openocd_program.sh new file mode 100755 index 0000000..6397252 --- /dev/null +++ b/openocd_program.sh @@ -0,0 +1,8 @@ +#!/bin/sh +if (( $# != 1 )); then + echo "Usage:" + echo "$0 " + exit 1 +fi + +openocd -f openocd.cfg -c "program $1 verify reset exit" diff --git a/src/delay.rs b/src/delay.rs new file mode 100644 index 0000000..2b1c5b4 --- /dev/null +++ b/src/delay.rs @@ -0,0 +1,74 @@ +//! Delays + +use cast::u32; +use cortex_m::peripheral::syst::SystClkSource; +use cortex_m::peripheral::SYST; + +use hal::blocking::delay::{DelayMs, DelayUs}; +use rcc::Clocks; + +/// System timer (SysTick) as a delay provider +pub struct Delay { + clocks: Clocks, + syst: SYST, +} + +impl Delay { + /// Configures the system timer (SysTick) as a delay provider + pub fn new(mut syst: SYST, clocks: Clocks) -> Self { + syst.set_clock_source(SystClkSource::Core); + + Delay { syst, clocks } + } + + /// Releases the system timer (SysTick) resource + pub fn free(self) -> SYST { + self.syst + } +} + +impl DelayMs for Delay { + fn delay_ms(&mut self, ms: u32) { + self.delay_us(ms * 1_000); + } +} + +impl DelayMs for Delay { + fn delay_ms(&mut self, ms: u16) { + self.delay_ms(u32(ms)); + } +} + +impl DelayMs for Delay { + fn delay_ms(&mut self, ms: u8) { + self.delay_ms(u32(ms)); + } +} + +impl DelayUs for Delay { + fn delay_us(&mut self, us: u32) { + let rvr = us * (self.clocks.sysclk().0 / 1_000_000); + + assert!(rvr < (1 << 24)); + + self.syst.set_reload(rvr); + self.syst.clear_current(); + self.syst.enable_counter(); + + while !self.syst.has_wrapped() {} + + self.syst.disable_counter(); + } +} + +impl DelayUs for Delay { + fn delay_us(&mut self, us: u16) { + self.delay_us(u32(us)) + } +} + +impl DelayUs for Delay { + fn delay_us(&mut self, us: u8) { + self.delay_us(u32(us)) + } +} diff --git a/src/gpio.rs b/src/gpio.rs new file mode 100644 index 0000000..bbaac1e --- /dev/null +++ b/src/gpio.rs @@ -0,0 +1,497 @@ +//! General Purpose Input / Output + +use core::marker::PhantomData; + +/// Extension trait to split a GPIO peripheral in independent pins and registers +pub trait GpioExt { + /// The parts to split the GPIO into + type Parts; + + /// Splits the GPIO block into independent pins and registers + fn split(self) -> Self::Parts; +} + +pub struct AF0; +pub struct AF1; +pub struct AF2; +pub struct AF3; +pub struct AF4; +pub struct AF5; +pub struct AF6; +pub struct AF7; + +pub struct Alternate { + _mode: PhantomData, +} + +/// Input mode (type state) +pub struct Input { + _mode: PhantomData, +} + +/// Floating input (type state) +pub struct Floating; + +/// Pulled down input (type state) +pub struct PullDown; + +/// Pulled up input (type state) +pub struct PullUp; + +/// Open drain input or output (type state) +pub struct OpenDrain; + +/// Output mode (type state) +pub struct Output { + _mode: PhantomData, +} + +/// Push pull output (type state) +pub struct PushPull; + +macro_rules! gpio { + ($GPIOX:ident, $gpiox:ident, $iopxenr:ident, $PXx:ident, [ + $($PXi:ident: ($pxi:ident, $i:expr, $MODE:ty),)+ + ]) => { + /// GPIO + pub mod $gpiox { + use core::marker::PhantomData; + + use hal::digital::{InputPin, OutputPin, StatefulOutputPin}; + use stm32::$GPIOX; + + use stm32::RCC; + use super::{ + Alternate, Floating, GpioExt, Input, OpenDrain, Output, + PullDown, PullUp, PushPull, AF0, AF1, AF2, AF3, AF4, AF5, AF6, AF7, + }; + + /// GPIO parts + pub struct Parts { + $( + /// Pin + pub $pxi: $PXi<$MODE>, + )+ + } + + impl GpioExt for $GPIOX { + type Parts = Parts; + + fn split(self) -> Parts { + // NOTE(unsafe) This executes only during initialisation + let rcc = unsafe { &(*RCC::ptr()) }; + rcc.ahbenr.modify(|_, w| w.$iopxenr().set_bit()); + + Parts { + $( + $pxi: $PXi { _mode: PhantomData }, + )+ + } + } + } + + /// Partially erased pin + pub struct $PXx { + i: u8, + _mode: PhantomData, + } + + impl StatefulOutputPin for $PXx> { + fn is_set_high(&self) -> bool { + !self.is_set_low() + } + + fn is_set_low(&self) -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*$GPIOX::ptr()).odr.read().bits() & (1 << self.i) == 0 } + } + } + + impl OutputPin for $PXx> { + fn set_high(&mut self) { + // NOTE(unsafe) atomic write to a stateless register + unsafe { (*$GPIOX::ptr()).bsrr.write(|w| w.bits(1 << self.i)) } + } + + fn set_low(&mut self) { + // NOTE(unsafe) atomic write to a stateless register + unsafe { (*$GPIOX::ptr()).bsrr.write(|w| w.bits(1 << (self.i + 16))) } + } + } + + impl InputPin for $PXx> { + fn is_high(&self) -> bool { + !self.is_low() + } + + fn is_low(&self) -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*$GPIOX::ptr()).idr.read().bits() & (1 << self.i) == 0 } + } + } + + fn _set_alternate_mode (index:usize, mode: u32) + { + let offset = 2 * index; + let offset2 = 4 * index; + unsafe { + if offset2 < 32 { + &(*$GPIOX::ptr()).afrl.modify(|r, w| { + w.bits((r.bits() & !(0b1111 << offset2)) | (mode << offset2)) + }); + } else + { + let offset2 = offset2 - 32; + &(*$GPIOX::ptr()).afrh.modify(|r, w| { + w.bits((r.bits() & !(0b1111 << offset2)) | (mode << offset2)) + }); + } + &(*$GPIOX::ptr()).moder.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b10 << offset)) + }); + } + } + + $( + /// Pin + pub struct $PXi { + _mode: PhantomData, + } + + impl $PXi { + /// Configures the pin to operate in AF0 mode + pub fn into_alternate_af0( + self, + ) -> $PXi> { + _set_alternate_mode($i, 0); + $PXi { _mode: PhantomData } + } + + /// Configures the pin to operate in AF1 mode + pub fn into_alternate_af1( + self, + ) -> $PXi> { + _set_alternate_mode($i, 1); + $PXi { _mode: PhantomData } + } + + /// Configures the pin to operate in AF2 mode + pub fn into_alternate_af2( + self, + ) -> $PXi> { + _set_alternate_mode($i, 2); + $PXi { _mode: PhantomData } + } + + /// Configures the pin to operate in AF3 mode + pub fn into_alternate_af3( + self, + ) -> $PXi> { + _set_alternate_mode($i, 3); + $PXi { _mode: PhantomData } + } + + /// Configures the pin to operate in AF4 mode + pub fn into_alternate_af4( + self, + ) -> $PXi> { + _set_alternate_mode($i, 4); + $PXi { _mode: PhantomData } + } + + /// Configures the pin to operate in AF5 mode + pub fn into_alternate_af5( + self, + ) -> $PXi> { + _set_alternate_mode($i, 5); + $PXi { _mode: PhantomData } + } + + /// Configures the pin to operate in AF6 mode + pub fn into_alternate_af6( + self, + ) -> $PXi> { + _set_alternate_mode($i, 6); + $PXi { _mode: PhantomData } + } + + /// Configures the pin to operate in AF7 mode + pub fn into_alternate_af7( + self, + ) -> $PXi> { + _set_alternate_mode($i, 7); + $PXi { _mode: PhantomData } + } + + /// Configures the pin to operate as a floating input pin + pub fn into_floating_input( + self, + ) -> $PXi> { + let offset = 2 * $i; + unsafe { + &(*$GPIOX::ptr()).pupdr.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b00 << offset)) + }); + &(*$GPIOX::ptr()).moder.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b00 << offset)) + }); + } + $PXi { _mode: PhantomData } + } + + /// Configures the pin to operate as a pulled down input pin + pub fn into_pull_down_input( + self, + ) -> $PXi> { + let offset = 2 * $i; + unsafe { + &(*$GPIOX::ptr()).pupdr.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b10 << offset)) + }); + &(*$GPIOX::ptr()).moder.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b00 << offset)) + }); + } + $PXi { _mode: PhantomData } + } + + /// Configures the pin to operate as a pulled up input pin + pub fn into_pull_up_input( + self, + ) -> $PXi> { + let offset = 2 * $i; + unsafe { + &(*$GPIOX::ptr()).pupdr.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b01 << offset)) + }); + &(*$GPIOX::ptr()).moder.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b00 << offset)) + }); + } + $PXi { _mode: PhantomData } + } + + /// Configures the pin to operate as an open drain output pin + pub fn into_open_drain_output( + self, + ) -> $PXi> { + let offset = 2 * $i; + unsafe { + &(*$GPIOX::ptr()).pupdr.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b00 << offset)) + }); + &(*$GPIOX::ptr()).otyper.modify(|r, w| { + w.bits(r.bits() | (0b1 << $i)) + }); + &(*$GPIOX::ptr()).moder.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b01 << offset)) + }); + } + $PXi { _mode: PhantomData } + } + + /// Configures the pin to operate as an push pull output pin + pub fn into_push_pull_output( + self, + ) -> $PXi> { + let offset = 2 * $i; + + unsafe { + &(*$GPIOX::ptr()).pupdr.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b00 << offset)) + }); + &(*$GPIOX::ptr()).otyper.modify(|r, w| { + w.bits(r.bits() & !(0b1 << $i)) + }); + &(*$GPIOX::ptr()).moder.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b01 << offset)) + }); + } + $PXi { _mode: PhantomData } + } + + /// Configures the pin to operate as an push pull output pin with quick fall + /// and rise times + pub fn into_push_pull_output_hs( + self, + ) -> $PXi> { + let offset = 2 * $i; + + unsafe { + &(*$GPIOX::ptr()).pupdr.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b00 << offset)) + }); + &(*$GPIOX::ptr()).otyper.modify(|r, w| { + w.bits(r.bits() & !(0b1 << $i)) + }); + &(*$GPIOX::ptr()).ospeedr.modify(|r, w| { + w.bits(r.bits() & !(0b1 << $i)) + }); + &(*$GPIOX::ptr()).moder.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b01 << offset)) + }); + } + + $PXi { _mode: PhantomData } + } + } + + impl $PXi> { + /// Enables / disables the internal pull up + pub fn internal_pull_up(&mut self, on: bool) { + let offset = 2 * $i; + let value = if on { 0b01 } else { 0b00 }; + unsafe { + &(*$GPIOX::ptr()).pupdr.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (value << offset)) + })}; + } + } + + impl $PXi> { + /// Enables / disables the internal pull up + pub fn internal_pull_up(self, on: bool) -> Self { + let offset = 2 * $i; + let value = if on { 0b01 } else { 0b00 }; + unsafe { + &(*$GPIOX::ptr()).pupdr.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (value << offset)) + })}; + + self + } + } + + impl $PXi> { + /// Turns pin alternate configuration pin into open drain + pub fn set_open_drain(self) -> Self { + let offset = $i; + unsafe { + &(*$GPIOX::ptr()).otyper.modify(|r, w| { + w.bits(r.bits() | (1 << offset)) + })}; + + self + } + } + + impl $PXi> { + /// Erases the pin number from the type + /// + /// This is useful when you want to collect the pins into an array where you + /// need all the elements to have the same type + pub fn downgrade(self) -> $PXx> { + $PXx { + i: $i, + _mode: self._mode, + } + } + } + + impl StatefulOutputPin for $PXi> { + fn is_set_high(&self) -> bool { + !self.is_set_low() + } + + fn is_set_low(&self) -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*$GPIOX::ptr()).odr.read().bits() & (1 << $i) == 0 } + } + } + + impl OutputPin for $PXi> { + fn set_high(&mut self) { + // NOTE(unsafe) atomic write to a stateless register + unsafe { (*$GPIOX::ptr()).bsrr.write(|w| w.bits(1 << $i)) } + } + + fn set_low(&mut self) { + // NOTE(unsafe) atomic write to a stateless register + unsafe { (*$GPIOX::ptr()).bsrr.write(|w| w.bits(1 << ($i + 16))) } + } + } + + impl $PXi> { + /// Erases the pin number from the type + /// + /// This is useful when you want to collect the pins into an array where you + /// need all the elements to have the same type + pub fn downgrade(self) -> $PXx> { + $PXx { + i: $i, + _mode: self._mode, + } + } + } + + impl InputPin for $PXi> { + fn is_high(&self) -> bool { + !self.is_low() + } + + fn is_low(&self) -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*$GPIOX::ptr()).idr.read().bits() & (1 << $i) == 0 } + } + } + )+ + + impl $PXx { + pub fn get_id (&self) -> u8 + { + self.i + } + } + } + } +} + +gpio!(GPIOA, gpioa, iopaen, PA, [ + PA0: (pa0, 0, Input), + PA1: (pa1, 1, Input), + PA2: (pa2, 2, Input), + PA3: (pa3, 3, Input), + PA4: (pa4, 4, Input), + PA5: (pa5, 5, Input), + PA6: (pa6, 6, Input), + PA7: (pa7, 7, Input), + PA8: (pa8, 8, Input), + PA9: (pa9, 9, Input), + PA10: (pa10, 10, Input), + PA11: (pa11, 11, Input), + PA12: (pa12, 12, Input), + PA13: (pa13, 13, Input), + PA14: (pa14, 14, Input), + PA15: (pa15, 15, Input), +]); + +gpio!(GPIOB, gpiob, iopben, PB, [ + PB0: (pb0, 0, Input), + PB1: (pb1, 1, Input), + PB2: (pb2, 2, Input), + PB3: (pb3, 3, Input), + PB4: (pb4, 4, Input), + PB5: (pb5, 5, Input), + PB6: (pb6, 6, Input), + PB7: (pb7, 7, Input), + PB8: (pb8, 8, Input), + PB9: (pb9, 9, Input), + PB10: (pb10, 10, Input), + PB11: (pb11, 11, Input), + PB12: (pb12, 12, Input), + PB13: (pb13, 13, Input), + PB14: (pb14, 14, Input), + PB15: (pb15, 15, Input), +]); + +gpio!(GPIOC, gpioc, iopcen, PC, [ + PC13: (pc13, 13, Input), + PC14: (pc14, 14, Input), + PC15: (pc15, 15, Input), +]); + +gpio!(GPIOF, gpiof, iopfen, PF, [ + PF0: (pf0, 0, Input), + PF1: (pf1, 1, Input), + PF11: (pf11, 11, Input), +]); diff --git a/src/i2c.rs b/src/i2c.rs new file mode 100644 index 0000000..e3293b1 --- /dev/null +++ b/src/i2c.rs @@ -0,0 +1,233 @@ +use stm32::{I2C1, RCC}; + +use hal::blocking::i2c::{Write, WriteRead}; + +use core::cmp; +use gpio::gpioa::{PA10, PA11, PA12, PA9}; +use gpio::gpiob::{PB10, PB11, PB13, PB14, PB6, PB7, PB8, PB9}; +use gpio::gpiof::{PF0, PF1}; +use gpio::{Alternate, AF1, AF4, AF5}; +use time::{KiloHertz, U32Ext}; + +/// I2C abstraction +pub struct I2c { + i2c: I2C, + pins: PINS, +} + +pub trait Pins {} + +impl Pins for (PA9>, PA10>) {} +impl Pins for (PA11>, PA12>) {} +impl Pins for (PB6>, PB7>) {} +impl Pins for (PB8>, PB9>) {} +impl Pins for (PB10>, PB11>) {} +impl Pins for (PB13>, PB14>) {} +impl Pins for (PF1>, PF0>) {} + +#[derive(Debug)] +pub enum Error { + OVERRUN, + NACK, +} + +impl I2c { + pub fn i2c1(i2c: I2C1, pins: PINS, speed: KiloHertz) -> Self + where + PINS: Pins, + { + // NOTE(unsafe) This executes only during initialisation + let rcc = unsafe { &(*RCC::ptr()) }; + + /* Enable clock for I2C1 */ + rcc.apb1enr.modify(|_, w| w.i2c1en().set_bit()); + + /* Reset I2C1 */ + rcc.apb1rstr.modify(|_, w| w.i2c1rst().set_bit()); + rcc.apb1rstr.modify(|_, w| w.i2c1rst().clear_bit()); + + /* Make sure the I2C unit is disabled so we can configure it */ + i2c.cr1.modify(|_, w| w.pe().clear_bit()); + + // Calculate settings for I2C speed modes + let presc; + let scldel; + let sdadel; + let sclh; + let scll; + + // We're using HSI here which runs at a fixed 8MHz + const FREQ: u32 = 8_000_000; + + // Normal I2C speeds use a different scaling than fast mode below + if speed <= 100_u32.khz() { + presc = 1; + scll = cmp::max((((FREQ >> presc) >> 1) / speed.0) - 1, 255) as u8; + sclh = scll - 4; + sdadel = 2; + scldel = 4; + } else { + presc = 0; + scll = cmp::max((((FREQ >> presc) >> 1) / speed.0) - 1, 255) as u8; + sclh = scll - 6; + sdadel = 1; + scldel = 3; + } + + /* Enable I2C signal generator, and configure I2C for 400KHz full speed */ + i2c.timingr.write(|w| { + w.presc() + .bits(presc) + .scldel() + .bits(scldel) + .sdadel() + .bits(sdadel) + .sclh() + .bits(sclh) + .scll() + .bits(scll) + }); + + /* Enable the I2C processing */ + i2c.cr1.modify(|_, w| w.pe().set_bit()); + + I2c { i2c, pins } + } + + pub fn release(self) -> (I2C1, PINS) { + (self.i2c, self.pins) + } + + fn send_byte(&self, byte: u8) -> Result<(), Error> { + /* Wait until we're ready for sending */ + while self.i2c.isr.read().txis().bit_is_clear() {} + + /* Push out a byte of data */ + self.i2c.txdr.write(|w| unsafe { w.bits(u32::from(byte)) }); + + /* If we received a NACK, then this is an error */ + if self.i2c.isr.read().nackf().bit_is_set() { + self.i2c + .icr + .write(|w| w.stopcf().set_bit().nackcf().set_bit()); + return Err(Error::NACK); + } + + Ok(()) + } + + fn recv_byte(&self) -> Result { + while self.i2c.isr.read().rxne().bit_is_clear() {} + let value = self.i2c.rxdr.read().bits() as u8; + Ok(value) + } +} + +impl WriteRead for I2c { + type Error = Error; + + fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { + /* Set up current address, we're trying a "read" command and not going to set anything + * and make sure we end a non-NACKed read (i.e. if we found a device) properly */ + self.i2c.cr2.modify(|_, w| { + w.sadd() + .bits(u16::from(addr) << 1) + .nbytes() + .bits(bytes.len() as u8) + .rd_wrn() + .clear_bit() + .autoend() + .clear_bit() + }); + + /* Send a START condition */ + self.i2c.cr2.modify(|_, w| w.start().set_bit()); + + /* Wait until the transmit buffer is empty and there hasn't been either a NACK or STOP + * being received */ + let mut isr; + while { + isr = self.i2c.isr.read(); + isr.txis().bit_is_clear() + && isr.nackf().bit_is_clear() + && isr.stopf().bit_is_clear() + && isr.tc().bit_is_clear() + } {} + + /* If we received a NACK, then this is an error */ + if isr.nackf().bit_is_set() { + self.i2c + .icr + .write(|w| w.stopcf().set_bit().nackcf().set_bit()); + return Err(Error::NACK); + } + + for c in bytes { + self.send_byte(*c)?; + } + + /* Wait until data was sent */ + while self.i2c.isr.read().tc().bit_is_clear() {} + + /* Set up current address, we're trying a "read" command and not going to set anything + * and make sure we end a non-NACKed read (i.e. if we found a device) properly */ + self.i2c.cr2.modify(|_, w| { + w.sadd() + .bits(u16::from(addr) << 1) + .nbytes() + .bits(buffer.len() as u8) + .rd_wrn() + .set_bit() + }); + + /* Send a START condition */ + self.i2c.cr2.modify(|_, w| w.start().set_bit()); + + /* Send the autoend after setting the start to get a restart */ + self.i2c.cr2.modify(|_, w| w.autoend().set_bit()); + + /* Read in all bytes */ + for c in buffer.iter_mut() { + *c = self.recv_byte()?; + } + + /* Clear flags if they somehow ended up set */ + self.i2c + .icr + .write(|w| w.stopcf().set_bit().nackcf().set_bit()); + + Ok(()) + } +} + +impl Write for I2c { + type Error = Error; + + fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { + /* Set up current address, we're trying a "read" command and not going to set anything + * and make sure we end a non-NACKed read (i.e. if we found a device) properly */ + self.i2c.cr2.modify(|_, w| { + w.sadd() + .bits(u16::from(addr) << 1) + .nbytes() + .bits(bytes.len() as u8) + .rd_wrn() + .clear_bit() + .autoend() + .set_bit() + }); + + /* Send a START condition */ + self.i2c.cr2.modify(|_, w| w.start().set_bit()); + + for c in bytes { + self.send_byte(*c)?; + } + + /* Fallthrough is success */ + self.i2c + .icr + .write(|w| w.stopcf().set_bit().nackcf().set_bit()); + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..46f83ae --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,32 @@ +#![no_std] +#![allow(non_camel_case_types)] + +extern crate bare_metal; +extern crate cast; +extern crate cortex_m; +pub extern crate embedded_hal as hal; + +pub extern crate void; +pub use void::Void; + +#[macro_use(block)] +pub extern crate nb; +pub use nb::block; + +pub extern crate stm32f0; + +#[cfg(feature = "stm32f042")] +pub use stm32f0::stm32f0x2 as stm32; + +// Enable use of interrupt macro +#[cfg(feature = "rt")] +pub use stm32f0::interrupt; + +pub mod delay; +pub mod gpio; +pub mod i2c; +pub mod prelude; +pub mod rcc; +pub mod serial; +pub mod spi; +pub mod time; diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..261f0a8 --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,5 @@ +pub use hal::prelude::*; + +pub use gpio::GpioExt as _stm32f042_hal_gpio_GpioExt; +pub use rcc::RccExt as _stm32f042_hal_rcc_RccExt; +pub use time::U32Ext as _stm32f042_hal_time_U32Ext; diff --git a/src/rcc.rs b/src/rcc.rs new file mode 100644 index 0000000..d582b9c --- /dev/null +++ b/src/rcc.rs @@ -0,0 +1,174 @@ +use core::cmp; + +use cast::u32; +use stm32::{FLASH, RCC}; + +use time::Hertz; + +/// Extension trait that constrains the `RCC` peripheral +pub trait RccExt { + /// Constrains the `RCC` peripheral so it plays nicely with the other abstractions + fn constrain(self) -> Rcc; +} + +impl RccExt for RCC { + fn constrain(self) -> Rcc { + Rcc { + cfgr: CFGR { + hclk: None, + pclk: None, + sysclk: None, + }, + } + } +} + +/// Constrained RCC peripheral +pub struct Rcc { + pub cfgr: CFGR, +} + +const HSI: u32 = 8_000_000; // Hz + +pub struct CFGR { + hclk: Option, + pclk: Option, + sysclk: Option, +} + +impl CFGR { + pub fn hclk(mut self, freq: F) -> Self + where + F: Into, + { + self.hclk = Some(freq.into().0); + self + } + + pub fn pclk(mut self, freq: F) -> Self + where + F: Into, + { + self.pclk = Some(freq.into().0); + self + } + + pub fn sysclk(mut self, freq: F) -> Self + where + F: Into, + { + self.sysclk = Some(freq.into().0); + self + } + + pub fn freeze(self) -> Clocks { + let pllmul = (4 * self.sysclk.unwrap_or(HSI) + HSI) / HSI / 2; + let pllmul = cmp::min(cmp::max(pllmul, 2), 16); + let sysclk = pllmul * HSI / 2; + + let pllmul_bits = if pllmul == 2 { + None + } else { + Some(pllmul as u8 - 2) + }; + + let hpre_bits = self + .hclk + .map(|hclk| match sysclk / hclk { + 0 => unreachable!(), + 1 => 0b0111, + 2 => 0b1000, + 3...5 => 0b1001, + 6...11 => 0b1010, + 12...39 => 0b1011, + 40...95 => 0b1100, + 96...191 => 0b1101, + 192...383 => 0b1110, + _ => 0b1111, + }) + .unwrap_or(0b0111); + + let hclk = sysclk / (1 << (hpre_bits - 0b0111)); + + let ppre_bits = self + .pclk + .map(|pclk| match hclk / pclk { + 0 => unreachable!(), + 1 => 0b011, + 2 => 0b100, + 3...5 => 0b101, + 6...11 => 0b110, + _ => 0b111, + }) + .unwrap_or(0b011); + + let ppre: u8 = 1 << (ppre_bits - 0b011); + let pclk = hclk / u32(ppre); + + // adjust flash wait states + unsafe { + let flash = &*FLASH::ptr(); + flash.acr.write(|w| { + w.latency().bits(if sysclk <= 24_000_000 { + 0b000 + } else if sysclk <= 48_000_000 { + 0b001 + } else { + 0b010 + }) + }) + } + + let rcc = unsafe { &*RCC::ptr() }; + if let Some(pllmul_bits) = pllmul_bits { + // use PLL as source + + rcc.cfgr.write(|w| unsafe { w.pllmul().bits(pllmul_bits) }); + + rcc.cr.write(|w| w.pllon().set_bit()); + + while rcc.cr.read().pllrdy().bit_is_clear() {} + + rcc.cfgr.modify(|_, w| unsafe { + w.ppre().bits(ppre_bits).hpre().bits(hpre_bits).sw().bits(2) + }); + } else { + // use HSI as source + rcc.cfgr + .write(|w| unsafe { w.ppre().bits(ppre_bits).hpre().bits(hpre_bits).sw().bits(0) }); + } + + Clocks { + hclk: Hertz(hclk), + pclk: Hertz(pclk), + sysclk: Hertz(sysclk), + } + } +} + +/// Frozen clock frequencies +/// +/// The existence of this value indicates that the clock configuration can no longer be changed +#[derive(Clone, Copy)] +pub struct Clocks { + hclk: Hertz, + pclk: Hertz, + sysclk: Hertz, +} + +impl Clocks { + /// Returns the frequency of the AHB + pub fn hclk(&self) -> Hertz { + self.hclk + } + + /// Returns the frequency of the APB + pub fn pclk(&self) -> Hertz { + self.pclk + } + + /// Returns the system (core) frequency + pub fn sysclk(&self) -> Hertz { + self.sysclk + } +} diff --git a/src/serial.rs b/src/serial.rs new file mode 100644 index 0000000..cd6a347 --- /dev/null +++ b/src/serial.rs @@ -0,0 +1,269 @@ +use core::fmt::{Result, Write}; +use core::marker::PhantomData; +use core::ptr; + +use hal; +use hal::prelude::*; +use nb; +use void::Void; + +use stm32::{RCC, USART1, USART2}; + +use gpio::gpioa::{PA10, PA14, PA15, PA2, PA3, PA9}; +use gpio::gpiob::{PB6, PB7}; +use gpio::{Alternate, AF0, AF1}; +use rcc::Clocks; +use time::Bps; + +/// Interrupt event +pub enum Event { + /// New data has been received + Rxne, + /// New data can be sent + Txe, +} + +/// Serial error +#[derive(Debug)] +pub enum Error { + /// Framing error + Framing, + /// Noise error + Noise, + /// RX buffer overrun + Overrun, + /// Parity check error + Parity, + #[doc(hidden)] + _Extensible, +} + +pub trait Pins {} + +impl Pins for (PA9>, PA10>) {} +impl Pins for (PB6>, PB7>) {} +impl Pins for (PA9>, PB7>) {} +impl Pins for (PB6>, PA10>) {} + +impl Pins for (PA2>, PA3>) {} +impl Pins for (PA2>, PA15>) {} +impl Pins for (PA14>, PA15>) {} +impl Pins for (PA14>, PA3>) {} + +/// Serial abstraction +pub struct Serial { + usart: USART, + pins: PINS, +} + +/// Serial receiver +pub struct Rx { + _usart: PhantomData, +} + +/// Serial transmitter +pub struct Tx { + _usart: PhantomData, +} + +/// USART1 +impl Serial { + pub fn usart1(usart: USART1, pins: PINS, baud_rate: Bps, clocks: Clocks) -> Self + where + PINS: Pins, + { + // NOTE(unsafe) This executes only during initialisation + let rcc = unsafe { &(*RCC::ptr()) }; + + /* Enable clock for USART */ + rcc.apb2enr.modify(|_, w| w.usart1en().set_bit()); + + // Calculate correct baudrate divisor on the fly + let brr = clocks.pclk().0 / baud_rate.0; + usart.brr.write(|w| unsafe { w.bits(brr) }); + + /* Reset other registers to disable advanced USART features */ + usart.cr2.reset(); + usart.cr3.reset(); + + /* Enable transmission and receiving */ + usart.cr1.modify(|_, w| unsafe { w.bits(0xD) }); + + Serial { usart, pins } + } + + pub fn split(self) -> (Tx, Rx) { + ( + Tx { + _usart: PhantomData, + }, + Rx { + _usart: PhantomData, + }, + ) + } + pub fn release(self) -> (USART1, PINS) { + (self.usart, self.pins) + } +} + +impl hal::serial::Read for Rx { + type Error = Error; + + fn read(&mut self) -> nb::Result { + // NOTE(unsafe) atomic read with no side effects + let isr = unsafe { (*USART1::ptr()).isr.read() }; + + Err(if isr.pe().bit_is_set() { + nb::Error::Other(Error::Parity) + } else if isr.fe().bit_is_set() { + nb::Error::Other(Error::Framing) + } else if isr.nf().bit_is_set() { + nb::Error::Other(Error::Noise) + } else if isr.ore().bit_is_set() { + nb::Error::Other(Error::Overrun) + } else if isr.rxne().bit_is_set() { + // NOTE(read_volatile) see `write_volatile` below + return Ok(unsafe { ptr::read_volatile(&(*USART1::ptr()).rdr as *const _ as *const _) }); + } else { + nb::Error::WouldBlock + }) + } +} + +impl hal::serial::Write for Tx { + type Error = Void; + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + // NOTE(unsafe) atomic read with no side effects + let isr = unsafe { (*USART1::ptr()).isr.read() }; + + if isr.tc().bit_is_set() { + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } + + fn write(&mut self, byte: u8) -> nb::Result<(), Self::Error> { + // NOTE(unsafe) atomic read with no side effects + let isr = unsafe { (*USART1::ptr()).isr.read() }; + + if isr.txe().bit_is_set() { + // NOTE(unsafe) atomic write to stateless register + // NOTE(write_volatile) 8-bit write that's not possible through the svd2rust API + unsafe { ptr::write_volatile(&(*USART1::ptr()).tdr as *const _ as *mut _, byte) } + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } +} + +/// USART2 +impl Serial { + pub fn usart2(usart: USART2, pins: PINS, baud_rate: Bps, clocks: Clocks) -> Self + where + PINS: Pins, + { + // NOTE(unsafe) This executes only during initialisation + let rcc = unsafe { &(*RCC::ptr()) }; + + /* Enable clock for USART */ + rcc.apb1enr.modify(|_, w| w.usart2en().set_bit()); + + // Calculate correct baudrate divisor on the fly + let brr = clocks.pclk().0 / baud_rate.0; + usart.brr.write(|w| unsafe { w.bits(brr) }); + + /* Reset other registers to disable advanced USART features */ + usart.cr2.reset(); + usart.cr3.reset(); + + /* Enable transmission and receiving */ + usart.cr1.modify(|_, w| unsafe { w.bits(0xD) }); + + Serial { usart, pins } + } + + pub fn split(self) -> (Tx, Rx) { + ( + Tx { + _usart: PhantomData, + }, + Rx { + _usart: PhantomData, + }, + ) + } + pub fn release(self) -> (USART2, PINS) { + (self.usart, self.pins) + } +} + +impl hal::serial::Read for Rx { + type Error = Error; + + fn read(&mut self) -> nb::Result { + // NOTE(unsafe) atomic read with no side effects + let isr = unsafe { (*USART2::ptr()).isr.read() }; + + Err(if isr.pe().bit_is_set() { + nb::Error::Other(Error::Parity) + } else if isr.fe().bit_is_set() { + nb::Error::Other(Error::Framing) + } else if isr.nf().bit_is_set() { + nb::Error::Other(Error::Noise) + } else if isr.ore().bit_is_set() { + nb::Error::Other(Error::Overrun) + } else if isr.rxne().bit_is_set() { + // NOTE(read_volatile) see `write_volatile` below + return Ok(unsafe { ptr::read_volatile(&(*USART2::ptr()).rdr as *const _ as *const _) }); + } else { + nb::Error::WouldBlock + }) + } +} + +impl hal::serial::Write for Tx { + type Error = Void; + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + // NOTE(unsafe) atomic read with no side effects + let isr = unsafe { (*USART2::ptr()).isr.read() }; + + if isr.tc().bit_is_set() { + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } + + fn write(&mut self, byte: u8) -> nb::Result<(), Self::Error> { + // NOTE(unsafe) atomic read with no side effects + let isr = unsafe { (*USART2::ptr()).isr.read() }; + + if isr.txe().bit_is_set() { + // NOTE(unsafe) atomic write to stateless register + // NOTE(write_volatile) 8-bit write that's not possible through the svd2rust API + unsafe { ptr::write_volatile(&(*USART2::ptr()).tdr as *const _ as *mut _, byte) } + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } +} + +impl Write for Tx +where + Tx: hal::serial::Write, +{ + fn write_str(&mut self, s: &str) -> Result { + let _ = s + .as_bytes() + .iter() + .map(|c| block!(self.write(*c))) + .last(); + Ok(()) + } +} diff --git a/src/spi.rs b/src/spi.rs new file mode 100644 index 0000000..4cd3352 --- /dev/null +++ b/src/spi.rs @@ -0,0 +1,170 @@ +use core::ptr; + +use nb; + +pub use hal::spi::{Mode, Phase, Polarity}; +use rcc::Clocks; + +use stm32::{RCC, SPI1}; + +use gpio::gpioa::{PA5, PA6, PA7}; +use gpio::gpiob::{PB3, PB4, PB5}; +use gpio::{Alternate, AF0}; +use time::Hertz; + +/// SPI error +#[derive(Debug)] +pub enum Error { + /// Overrun occurred + Overrun, + /// Mode fault occurred + ModeFault, + /// CRC error + Crc, + #[doc(hidden)] + _Extensible, +} + +/// SPI abstraction +pub struct Spi { + spi: SPI, + pins: PINS, +} + +pub trait Pins {} + +impl Pins + for ( + PA5>, + PA6>, + PA7>, + ) +{} +impl Pins + for ( + PB3>, + PB4>, + PB5>, + ) +{} + +impl Spi { + pub fn spi1(spi: SPI1, pins: PINS, mode: Mode, speed: F, clocks: Clocks) -> Self + where + PINS: Pins, + F: Into, + { + // NOTE(unsafe) This executes only during initialisation + let rcc = unsafe { &(*RCC::ptr()) }; + + /* Enable clock for SPI1 */ + rcc.apb2enr.modify(|_, w| w.spi1en().set_bit()); + + /* Reset SPI1 */ + rcc.apb2rstr.modify(|_, w| w.spi1rst().set_bit()); + rcc.apb2rstr.modify(|_, w| w.spi1rst().clear_bit()); + + /* Make sure the SPI unit is disabled so we can configure it */ + spi.cr1.modify(|_, w| w.spe().clear_bit()); + + // FRXTH: 8-bit threshold on RX FIFO + // DS: 8-bit data size + // SSOE: cleared to disable SS output + // + // NOTE(unsafe): DS reserved bit patterns are 0b0000, 0b0001, and 0b0010. 0b0111 is valid + // (reference manual, pp 804) + spi.cr2 + .write(|w| unsafe { w.frxth().set_bit().ds().bits(0b0111).ssoe().clear_bit() }); + + let br = match clocks.pclk().0 / speed.into().0 { + 0 => unreachable!(), + 1...2 => 0b000, + 3...5 => 0b001, + 6...11 => 0b010, + 12...23 => 0b011, + 24...47 => 0b100, + 48...95 => 0b101, + 96...191 => 0b110, + _ => 0b111, + }; + + // mstr: master configuration + // lsbfirst: MSB first + // ssm: enable software slave management (NSS pin free for other uses) + // ssi: set nss high = master mode + // dff: 8 bit frames + // bidimode: 2-line unidirectional + // spe: enable the SPI bus + spi.cr1.write(|w| unsafe { + w.cpha() + .bit(mode.phase == Phase::CaptureOnSecondTransition) + .cpol() + .bit(mode.polarity == Polarity::IdleHigh) + .mstr() + .set_bit() + .br() + .bits(br) + .lsbfirst() + .clear_bit() + .ssm() + .set_bit() + .ssi() + .set_bit() + .rxonly() + .clear_bit() + .bidimode() + .clear_bit() + .spe() + .set_bit() + }); + + Spi { spi, pins } + } + + pub fn release(self) -> (SPI1, PINS) { + (self.spi, self.pins) + } +} + +impl ::hal::spi::FullDuplex for Spi { + type Error = Error; + + fn read(&mut self) -> nb::Result { + let sr = self.spi.sr.read(); + + Err(if sr.ovr().bit_is_set() { + nb::Error::Other(Error::Overrun) + } else if sr.modf().bit_is_set() { + nb::Error::Other(Error::ModeFault) + } else if sr.crcerr().bit_is_set() { + nb::Error::Other(Error::Crc) + } else if sr.rxne().bit_is_set() { + // NOTE(read_volatile) read only 1 byte (the svd2rust API only allows + // reading a half-word) + return Ok(unsafe { ptr::read_volatile(&self.spi.dr as *const _ as *const u8) }); + } else { + nb::Error::WouldBlock + }) + } + + fn send(&mut self, byte: u8) -> nb::Result<(), Error> { + let sr = self.spi.sr.read(); + + Err(if sr.ovr().bit_is_set() { + nb::Error::Other(Error::Overrun) + } else if sr.modf().bit_is_set() { + nb::Error::Other(Error::ModeFault) + } else if sr.crcerr().bit_is_set() { + nb::Error::Other(Error::Crc) + } else if sr.txe().bit_is_set() { + // NOTE(write_volatile) see note above + unsafe { ptr::write_volatile(&self.spi.dr as *const _ as *mut u8, byte) } + return Ok(()); + } else { + nb::Error::WouldBlock + }) + } +} + +impl ::hal::blocking::spi::transfer::Default for Spi {} +impl ::hal::blocking::spi::write::Default for Spi {} diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..cf67ff1 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,63 @@ +/// Bits per second +#[derive(PartialEq, PartialOrd, Clone, Copy)] +pub struct Bps(pub u32); + +#[derive(PartialEq, PartialOrd, Clone, Copy)] +pub struct Hertz(pub u32); + +#[derive(PartialEq, PartialOrd, Clone, Copy)] +pub struct KiloHertz(pub u32); + +#[derive(PartialEq, PartialOrd, Clone, Copy)] +pub struct MegaHertz(pub u32); + +/// Extension trait that adds convenience methods to the `u32` type +pub trait U32Ext { + /// Wrap in `Bps` + fn bps(self) -> Bps; + + /// Wrap in `Hertz` + fn hz(self) -> Hertz; + + /// Wrap in `KiloHertz` + fn khz(self) -> KiloHertz; + + /// Wrap in `MegaHertz` + fn mhz(self) -> MegaHertz; +} + +impl U32Ext for u32 { + fn bps(self) -> Bps { + Bps(self) + } + + fn hz(self) -> Hertz { + Hertz(self) + } + + fn khz(self) -> KiloHertz { + KiloHertz(self) + } + + fn mhz(self) -> MegaHertz { + MegaHertz(self) + } +} + +impl Into for KiloHertz { + fn into(self) -> Hertz { + Hertz(self.0 * 1_000) + } +} + +impl Into for MegaHertz { + fn into(self) -> Hertz { + Hertz(self.0 * 1_000_000) + } +} + +impl Into for MegaHertz { + fn into(self) -> KiloHertz { + KiloHertz(self.0 * 1_000) + } +} diff --git a/tools/capture_example_bloat.sh b/tools/capture_example_bloat.sh new file mode 100755 index 0000000..a291e06 --- /dev/null +++ b/tools/capture_example_bloat.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +filename="bloat_log_"`date -Iminutes`".txt" + +for i in `find examples -name "*.rs"`; do + name=$(echo $i | sed -e "s,examples/,,g" -e "s,\.rs,,g") + echo "Processing example $name" + echo >>$filename + echo "Bloat for example $name" >>$filename + cargo bloat --release --features=stm32f042,rt --example $name >>$filename +done + +echo "Captures bloat for all examples into $filename"