diff --git a/.cargo/config b/.cargo/config index 3500668..4dc2c04 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,3 +1,11 @@ [build] # Instruction set of Cortex-M0+ target = "thumbv6m-none-eabi" + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "inline-threshold=5", + "-C", "no-vectorize-loops", +] diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index c0f5358..46e6e93 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -6,6 +6,8 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 + - run: sudo apt-get update + - run: sudo apt-get install gcc-arm-none-eabi - uses: actions-rs/toolchain@v1 with: toolchain: stable @@ -15,4 +17,4 @@ jobs: - uses: actions-rs/cargo@v1 with: command: check - args: --workspace + args: --workspace --examples diff --git a/memory.x b/memory.x new file mode 100644 index 0000000..23918e1 --- /dev/null +++ b/memory.x @@ -0,0 +1,13 @@ +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} + +SECTIONS { + /* ### Boot loader */ + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; diff --git a/rp2040-hal/Cargo.toml b/rp2040-hal/Cargo.toml index b0c0238..192cdbf 100644 --- a/rp2040-hal/Cargo.toml +++ b/rp2040-hal/Cargo.toml @@ -11,7 +11,12 @@ license = "MIT OR Apache-2.0" [dependencies] cortex-m = "0.7.1" -embedded-hal = "0.2.4" -nb = "1.0.0" -rp2040-pac = "0.1.1" +embedded-hal = { version = "0.2.4", features = ["unproven"] } embedded-time = "0.10.1" +nb = "1.0.0" +rp2040-pac = { git = "https://github.com/rp-rs/rp2040-pac", branch="main" } + +[dev-dependencies] +cortex-m-rt = "0.6.13" +panic-halt = "0.2.0" +rp2040-boot2 = { git = "https://github.com/rp-rs/rp2040-boot2-rs", branch="main" } \ No newline at end of file diff --git a/rp2040-hal/examples/blinky.rs b/rp2040-hal/examples/blinky.rs new file mode 100644 index 0000000..2bae343 --- /dev/null +++ b/rp2040-hal/examples/blinky.rs @@ -0,0 +1,30 @@ +//! Blinks the LED on a Pico board +//! +//! This will blink an LED attached to GP25, which is the pin the Pico uses for the on-board LED. +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use embedded_hal::digital::v2::OutputPin; +use panic_halt as _; +use rp2040_hal::prelude::*; + +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER; + +#[entry] +fn main() -> ! { + let mut pac = rp2040_pac::Peripherals::take().unwrap(); + + let pins = pac.IO_BANK0.split(pac.PADS_BANK0, pac.SIO, &mut pac.RESETS); + let mut led_pin = pins.gpio25.into_output(); + + loop { + led_pin.set_high().unwrap(); + // TODO: Replace with proper 1s delays once we have clocks working + cortex_m::asm::delay(500_000); + led_pin.set_low().unwrap(); + cortex_m::asm::delay(500_000); + } +} diff --git a/rp2040-hal/examples/gpio_in_out.rs b/rp2040-hal/examples/gpio_in_out.rs new file mode 100644 index 0000000..963b150 --- /dev/null +++ b/rp2040-hal/examples/gpio_in_out.rs @@ -0,0 +1,33 @@ +//! Toggle LED based on GPIO input +//! +//! This will control an LED on GP25 based on a button hooked up to GP15. The button should be tied +//! to ground, as the input pin is pulled high internally by this example. When the button is +//! pressed, the LED will turn off. +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use embedded_hal::digital::v2::{InputPin, OutputPin}; +use panic_halt as _; +use rp2040_hal::prelude::*; + +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER; + +#[entry] +fn main() -> ! { + let mut pac = rp2040_pac::Peripherals::take().unwrap(); + + let pins = pac.IO_BANK0.split(pac.PADS_BANK0, pac.SIO, &mut pac.RESETS); + let mut led_pin = pins.gpio25.into_output(); + let button_pin = pins.gpio15.into_input().pull_up(); + + loop { + if button_pin.is_high().unwrap() { + led_pin.set_high().unwrap(); + } else { + led_pin.set_low().unwrap(); + } + } +} diff --git a/rp2040-hal/src/gpio.rs b/rp2040-hal/src/gpio.rs new file mode 100644 index 0000000..e954257 --- /dev/null +++ b/rp2040-hal/src/gpio.rs @@ -0,0 +1,300 @@ +//! General Purpose Input and Output (GPIO) +//! +//! To access the GPIO pins you must call the `split` method on the IO bank. This will return a +//! `Parts` struct with access to the individual pins: +//! +//! ```rust +//! use rp2040_hal::prelude::*; +//! let mut pac = rp2040_pac::Peripherals::take().unwrap(); +//! let pins = pac.IO_BANK0.split(pac.PADS_BANK0, pac.SIO, &mut pac.RESETS); +//! ``` +//! +//! Once you have the GPIO pins struct, you can take individual pins and configure them: +//! +//! ```rust +//! let mut led_pin = pins.gpio25.into_output(); +//! led_pin.set_high().unwrap(); +//! ``` +//! +//! Input pins support the following options: +//! - Pull high, pull low, or floating +//! - Schmitt trigger +//! +//! Output pins support the following options: +//! - Slew rate (fast or slow) +//! - Drive strength (2, 4, 8 or 12 mA) + +/// Mode marker for an input pin +pub struct Input; +/// Mode marker for an output pin +pub struct Output; +/// Mode marker for a pin in an unknown state (generally happens at startup) +pub struct Unknown; + +/// This trait adds a method to extract pins from an IO bank and convert them into HAL objects +pub trait GpioExt { + /// The type of struct that will hold the pins once they're converted to HAL objects + type Parts; + + /// Convert the IO bank into a struct of HAL pin objects + // TODO: Do we need a marker to check that clocks are up? + fn split(self, pads: PADS, sio: SIO, reset: &mut rp2040_pac::RESETS) -> Self::Parts; +} + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +/// The amount of current that a pin can drive when used as an output +pub enum OutputDriveStrength { + /// 2 mA + TwoMilliAmps, + /// 4 mA + FourMilliAmps, + /// 8 mA + EightMilliAmps, + /// 12 mA + TwelveMilliAmps, +} + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +/// The slew rate of a pin when used as an output +pub enum OutputSlewRate { + /// Slew slow + Slow, + /// Slew fast + Fast, +} + +macro_rules! gpio { + ($GPIOX:ident, $gpiox:ident, $PADSX:ident, $padsx:ident, $gpioxs:expr, [ + $($PXi:ident: ($pxi:ident, $i:expr, $is:expr),)+ + ]) => { + #[doc = "HAL objects for the "] + #[doc = $gpioxs] + #[doc = " bank of GPIO pins"] + pub mod $gpiox { + use core::convert::Infallible; + use core::marker::PhantomData; + use embedded_hal::digital::v2::{InputPin, OutputPin, StatefulOutputPin}; + use super::*; + + impl GpioExt for pac::$GPIOX { + type Parts = Parts; + + fn split(self, pads: pac::$PADSX, sio: pac::SIO, resets: &mut pac::RESETS) -> Parts { + resets.reset.modify(|_, w| w.$gpiox().clear_bit().$padsx().clear_bit()); + // TODO: Implement Resets in the HAL + while resets.reset_done.read().$gpiox().bit_is_clear() { + cortex_m::asm::delay(10); + } + while resets.reset_done.read().$padsx().bit_is_clear() { + cortex_m::asm::delay(10); + } + Parts { + _pads: pads, + _sio: sio, + $( + $pxi: $PXi { _mode: PhantomData }, + )+ + } + } + } + + #[doc = "Struct containing HAL objects for all the "] + #[doc = $gpioxs] + #[doc = " pins"] + pub struct Parts { + _pads: pac::$PADSX, + _sio: pac::SIO, + $( + #[doc = "GPIO pin "] + #[doc = $is] + pub $pxi: $PXi, + )+ + } + + type PacDriveStrength = pac::$padsx::gpio::DRIVE_A; + + $( + #[doc = "HAL object for GPIO pin "] + #[doc = $is] + pub struct $PXi { + _mode: PhantomData, + } + + impl $PXi { + // This is safe because Parts owns the pads, and each pin is responsible + // for its own pad + fn pad(&self) -> &pac::$padsx::GPIO { + unsafe { + &(*pac::$PADSX::ptr()).gpio[$i] + } + } + + // This is safe because Parts owns the SIO. But callers must only touch their + // own pin + fn sio(&self) -> &pac::sio::RegisterBlock { + unsafe { + &(*pac::SIO::ptr()) + } + } + + // This is safe because Parts owns the bank, and each pin is responsible + // for its own slice of the bank + fn gpio_ctrl(&self) -> &pac::$gpiox::gpio::GPIO_CTRL { + unsafe { + &(*pac::$GPIOX::ptr()).gpio[$i].gpio_ctrl + } + } + + #[doc = "Configure this pin as an output"] + pub fn into_output(self)-> $PXi { + self.pad().reset(); + self.gpio_ctrl().write_with_zero(|x| { x.funcsel().sio_0() }); + // TODO: Can we update the PAC to give us a safe register field + // instead of `bits`? + self.sio().gpio_oe_set.write(|x| unsafe { x.bits(1 << $i) }); + $PXi { _mode: PhantomData } + } + + #[doc = "Configure this pin as an input"] + pub fn into_input(self) -> $PXi { + self.pad().reset(); + self.gpio_ctrl().write_with_zero(|x| { x.funcsel().sio_0() }); + self.sio().gpio_oe_clr.write(|x| unsafe { x.bits(1 << $i) }); + + $PXi { _mode: PhantomData } + } + } + + impl OutputPin for $PXi { + type Error = Infallible; + + fn set_low(&mut self) -> Result<(), Self::Error> { + self.sio().gpio_out_clr.write(|x| unsafe { x.bits(1 << $i) }); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self.sio().gpio_out_set.write(|x| unsafe { x.bits(1 << $i) }); + Ok(()) + } + } + + impl StatefulOutputPin for $PXi { + fn is_set_low(&self) -> Result { + Ok(!self.is_set_high()?) + } + + fn is_set_high(&self) -> Result { + Ok(self.sio().gpio_out.read().bits() & (1 << $i) != 0) + } + } + + macro_rules! impl_input_for { + ($MODE:ident) => { + impl InputPin for $PXi<$MODE> { + type Error = Infallible; + + fn is_low(&self) -> Result { + Ok(!self.is_high()?) + } + + fn is_high(&self) -> Result { + Ok(self.sio().gpio_in.read().bits() & (1 << $i) != 0) + } + } + }; + } + // Not allowed for Unknown since we don't know what state the pad is in + impl_input_for!(Input); + impl_input_for!(Output); + + impl $PXi { + #[doc = "Configure the drive strength for this output pin"] + pub fn drive_strength(self, strength: OutputDriveStrength) -> Self { + let converted = match strength { + OutputDriveStrength::TwoMilliAmps => PacDriveStrength::_2MA, + OutputDriveStrength::FourMilliAmps => PacDriveStrength::_4MA, + OutputDriveStrength::EightMilliAmps => PacDriveStrength::_8MA, + OutputDriveStrength::TwelveMilliAmps => PacDriveStrength::_12MA, + }; + self.pad().modify(|_, w| w.drive().variant(converted)); + self + } + + #[doc = "Configure the slew rate for this output pin"] + pub fn slew_rate(self, slew_rate: OutputSlewRate) -> Self { + self.pad().modify(|_, w| w.slewfast().bit(slew_rate == OutputSlewRate::Fast)); + self + } + } + + impl $PXi { + #[doc = "Pull this input pin high using internal resistors"] + pub fn pull_up(self) -> Self { + self.pad().modify(|_, w| w.pue().set_bit().pde().clear_bit()); + self + } + + #[doc = "Pull this input pin low using internal resistors"] + pub fn pull_down(self) -> Self { + self.pad().modify(|_, w| w.pue().clear_bit().pde().set_bit()); + self + } + + #[doc = "Allow this input pin to float (i.e. don't pull it high or low)"] + pub fn float(self) -> Self { + self.pad().modify(|_, w| w.pue().clear_bit().pde().clear_bit()); + self + } + + #[doc = "Enable the schmitt trigger for this input pin"] + pub fn enable_schmitt_trigger(self) -> Self { + self.pad().modify(|_, w| w.schmitt().set_bit()); + self + } + + #[doc = "Disable the schmitt trigger for this input pin"] + pub fn disable_schmitt_trigger(self) -> Self { + self.pad().modify(|_, w| w.schmitt().clear_bit()); + self + } + } + )+ + } + }; +} + +gpio!( + IO_BANK0, io_bank0, PADS_BANK0, pads_bank0, "IO_BANK0", [ + Gpio0: (gpio0, 0, "0"), + Gpio1: (gpio1, 1, "1"), + Gpio2: (gpio2, 2, "2"), + Gpio3: (gpio3, 3, "3"), + Gpio4: (gpio4, 4, "4"), + Gpio5: (gpio5, 5, "5"), + Gpio6: (gpio6, 6, "6"), + Gpio7: (gpio7, 7, "7"), + Gpio8: (gpio8, 8, "8"), + Gpio9: (gpio9, 9, "9"), + Gpio10: (gpio10, 10, "10"), + Gpio11: (gpio11, 11, "11"), + Gpio12: (gpio12, 12, "12"), + Gpio13: (gpio13, 13, "13"), + Gpio14: (gpio14, 14, "14"), + Gpio15: (gpio15, 15, "15"), + Gpio16: (gpio16, 16, "16"), + Gpio17: (gpio17, 17, "17"), + Gpio18: (gpio18, 18, "18"), + Gpio19: (gpio19, 19, "19"), + Gpio20: (gpio20, 20, "20"), + Gpio21: (gpio21, 21, "21"), + Gpio22: (gpio22, 22, "22"), + Gpio23: (gpio23, 23, "23"), + Gpio24: (gpio24, 24, "24"), + Gpio25: (gpio25, 25, "25"), + Gpio26: (gpio26, 26, "26"), + Gpio27: (gpio27, 27, "27"), + Gpio28: (gpio28, 28, "28"), + Gpio29: (gpio29, 29, "29"), + ] +); diff --git a/rp2040-hal/src/lib.rs b/rp2040-hal/src/lib.rs index 477db61..58dbbaa 100644 --- a/rp2040-hal/src/lib.rs +++ b/rp2040-hal/src/lib.rs @@ -14,6 +14,7 @@ extern crate nb; pub extern crate rp2040_pac as pac; pub mod adc; +pub mod gpio; pub mod i2c; pub mod prelude; pub mod pwm; diff --git a/rp2040-hal/src/prelude.rs b/rp2040-hal/src/prelude.rs index 8e3e645..57edf4f 100644 --- a/rp2040-hal/src/prelude.rs +++ b/rp2040-hal/src/prelude.rs @@ -1 +1,2 @@ //! Prelude +pub use crate::gpio::GpioExt;