From 8d32995b21e7bd3a3a9e60c1e05a23c9bafe80a3 Mon Sep 17 00:00:00 2001 From: Adam Gausmann Date: Tue, 25 Oct 2022 21:27:33 -0500 Subject: [PATCH 1/3] Add avrxmega crate; add port implementations for xmega / attiny404 --- Cargo.toml | 1 + avr-hal-generic/src/port.rs | 231 +++++++++++++++++++++++++++++++++++ mcu/avrxmega-hal/Cargo.toml | 28 +++++ mcu/avrxmega-hal/src/lib.rs | 55 +++++++++ mcu/avrxmega-hal/src/port.rs | 25 ++++ 5 files changed, 340 insertions(+) create mode 100644 mcu/avrxmega-hal/Cargo.toml create mode 100644 mcu/avrxmega-hal/src/lib.rs create mode 100644 mcu/avrxmega-hal/src/port.rs diff --git a/Cargo.toml b/Cargo.toml index ae61c621bb..2b90668d2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ members = [ # MCU HAL crates "mcu/atmega-hal", "mcu/attiny-hal", + "mcu/avrxmega-hal", # Higher level crates "arduino-hal", diff --git a/avr-hal-generic/src/port.rs b/avr-hal-generic/src/port.rs index e880afba99..68bd6a8518 100644 --- a/avr-hal-generic/src/port.rs +++ b/avr-hal-generic/src/port.rs @@ -663,6 +663,237 @@ macro_rules! impl_port_traditional { }; } +#[macro_export] +macro_rules! impl_port_xmega { + ( + enum Ports { + $($PortName:ident: ($Port:ty),)+ + } + + $(#[$pins_attr:meta])* + pub struct Pins { + $($pin:ident: $Pin:ident = ($PinPort:ty, $PinPortName:ident, $pin_num:expr, $pin_pinctrl_reg:ident),)+ + } + ) => { + /// Type-alias for a pin type which can represent any concrete pin. + /// + /// Sometimes it is easier to handle pins if they are all of the same type. By default, + /// each pin gets its own distinct type in `avr-hal`, but by + /// [downgrading][avr_hal_generic::port::Pin#downgrading], you can cast them into this + /// "dynamic" type. Do note, however, that using this dynamic type has a runtime cost. + pub type Pin = $crate::port::Pin; + + $(#[$pins_attr])* + pub struct Pins { + $(pub $pin: Pin< + mode::Input, + $Pin, + >,)+ + } + + impl Pins { + pub fn new( + $(_: $Port,)+ + ) -> Self { + Self { + $($pin: $crate::port::Pin::new( + $Pin { _private: (), } + ),)+ + } + } + } + + #[repr(u8)] + pub enum DynamicPort { + $($PortName,)+ + } + + pub struct Dynamic { + port: DynamicPort, + // We'll store the mask instead of the pin number because this allows much less code to + // be generated for the trait method implementations. + // + // (TODO verify this statement quantitatively? This implementation detail was + // copied from impl_port_traditional, but it makes some things more complicated, + // like accessing PINxCTRL to configure pull-up. + mask: u8, + } + + impl Dynamic { + fn new(port: DynamicPort, pin_num: u8) -> Self { + Self { + port, + mask: 1 << pin_num, + } + } + } + + impl $crate::port::PinOps for Dynamic { + type Dynamic = Self; + + #[inline] + fn into_dynamic(self) -> Self::Dynamic { + self + } + + #[inline] + unsafe fn out_set(&mut self) { + match self.port { + $(DynamicPort::$PortName => (*<$Port>::ptr()).outset.write(|w| { + w.bits(self.mask) + }),)+ + } + } + + #[inline] + unsafe fn out_clear(&mut self) { + match self.port { + $(DynamicPort::$PortName => (*<$Port>::ptr()).outclr.write(|w| { + w.bits(self.mask) + }),)+ + } + } + + #[inline] + unsafe fn out_toggle(&mut self) { + match self.port { + $(DynamicPort::$PortName => (*<$Port>::ptr()).outtgl.write(|w| { + w.bits(self.mask) + }),)+ + } + } + + #[inline] + unsafe fn out_get(&self) -> bool { + match self.port { + $(DynamicPort::$PortName => (*<$Port>::ptr()).out.read().bits() + & self.mask != 0,)+ + } + } + + #[inline] + unsafe fn in_get(&self) -> bool { + match self.port { + $(DynamicPort::$PortName => (*<$Port>::ptr()).in_.read().bits() + & self.mask != 0,)+ + } + } + + #[inline] + unsafe fn make_output(&mut self) { + match self.port { + $(DynamicPort::$PortName => (*<$Port>::ptr()).dirset.write(|w| { + w.bits(self.mask) + }),)+ + } + } + + #[inline] + unsafe fn make_input(&mut self, pull_up: bool) { + match self.port { + $(DynamicPort::$PortName => { + if pull_up { + match self.mask { + 0x01 => (*<$Port>::ptr()).pin0ctrl.modify(|_, w| w.pullupen().set_bit()), + 0x02 => (*<$Port>::ptr()).pin1ctrl.modify(|_, w| w.pullupen().set_bit()), + 0x04 => (*<$Port>::ptr()).pin2ctrl.modify(|_, w| w.pullupen().set_bit()), + 0x08 => (*<$Port>::ptr()).pin3ctrl.modify(|_, w| w.pullupen().set_bit()), + 0x10 => (*<$Port>::ptr()).pin4ctrl.modify(|_, w| w.pullupen().set_bit()), + 0x20 => (*<$Port>::ptr()).pin5ctrl.modify(|_, w| w.pullupen().set_bit()), + 0x40 => (*<$Port>::ptr()).pin6ctrl.modify(|_, w| w.pullupen().set_bit()), + 0x80 => (*<$Port>::ptr()).pin7ctrl.modify(|_, w| w.pullupen().set_bit()), + // TODO exhaustive match with an enum? + //_ => unreachable!() + _ => {} + } + } else { + match self.mask { + 0x01 => (*<$Port>::ptr()).pin0ctrl.modify(|_, w| w.pullupen().clear_bit()), + 0x02 => (*<$Port>::ptr()).pin1ctrl.modify(|_, w| w.pullupen().clear_bit()), + 0x04 => (*<$Port>::ptr()).pin2ctrl.modify(|_, w| w.pullupen().clear_bit()), + 0x08 => (*<$Port>::ptr()).pin3ctrl.modify(|_, w| w.pullupen().clear_bit()), + 0x10 => (*<$Port>::ptr()).pin4ctrl.modify(|_, w| w.pullupen().clear_bit()), + 0x20 => (*<$Port>::ptr()).pin5ctrl.modify(|_, w| w.pullupen().clear_bit()), + 0x40 => (*<$Port>::ptr()).pin6ctrl.modify(|_, w| w.pullupen().clear_bit()), + 0x80 => (*<$Port>::ptr()).pin7ctrl.modify(|_, w| w.pullupen().clear_bit()), + // TODO exhaustive match with an enum? + //_ => unreachable!() + _ => {} + } + } + (*<$Port>::ptr()).dirclr.write(|w| { + w.bits(self.mask) + }); + })+ + } + } + } + + $( + pub struct $Pin { + _private: () + } + + impl $crate::port::PinOps for $Pin { + type Dynamic = Dynamic; + + #[inline] + fn into_dynamic(self) -> Self::Dynamic { + Dynamic::new(DynamicPort::$PinPortName, $pin_num) + } + + #[inline] + unsafe fn out_set(&mut self) { + (*<$PinPort>::ptr()).outset.write(|w| { + w.bits(1 << $pin_num) + }); + } + + #[inline] + unsafe fn out_clear(&mut self) { + (*<$PinPort>::ptr()).outclr.write(|w| { + w.bits(1 << $pin_num) + }); + } + + #[inline] + unsafe fn out_toggle(&mut self) { + (*<$PinPort>::ptr()).outtgl.write(|w| w.bits(1 << $pin_num)); + } + + #[inline] + unsafe fn out_get(&self) -> bool { + (*<$PinPort>::ptr()).out.read().bits() & (1 << $pin_num) != 0 + } + + #[inline] + unsafe fn in_get(&self) -> bool { + (*<$PinPort>::ptr()).in_.read().bits() & (1 << $pin_num) != 0 + } + + #[inline] + unsafe fn make_output(&mut self) { + (*<$PinPort>::ptr()).dirset.write(|w| { + w.bits(1 << $pin_num) + }); + } + + #[inline] + unsafe fn make_input(&mut self, pull_up: bool) { + if pull_up { + (*<$PinPort>::ptr()).$pin_pinctrl_reg.modify(|_, w| w.pullupen().set_bit()); + } else { + (*<$PinPort>::ptr()).$pin_pinctrl_reg.modify(|_, w| w.pullupen().clear_bit()); + } + (*<$PinPort>::ptr()).dirclr.write(|w| { + w.bits(1 << $pin_num) + }); + } + } + )+ + }; +} + #[macro_export] macro_rules! renamed_pins { ( diff --git a/mcu/avrxmega-hal/Cargo.toml b/mcu/avrxmega-hal/Cargo.toml new file mode 100644 index 0000000000..c20bebce49 --- /dev/null +++ b/mcu/avrxmega-hal/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "avrxmega-hal" +version = "0.1.0" +edition = "2018" + +[features] +rt = ["avr-device/rt"] +device-selected = [] +attiny404 = ["avr-device/attiny404", "device-selected"] + +critical-section-impl = ["avr-device/critical-section-impl"] + +# Allow certain downstream crates to overwrite the device selection error by themselves. +disable-device-selection-error = [] + +[dependencies] +avr-hal-generic = { path = "../../avr-hal-generic/" } + +[dependencies.avr-device] +version = "0.5" + +# Because this crate has its own check that at least one device is selected, we +# can safely "circumvent" the check in `avr-device`. +# +# Why would we want that? Otherwise, as `avr-device` is compiled first, its +# error will be shown and ours won't which leads to a degraded user experience +# as the displayed error message does not really tell what needs to be done... +features = ["device-selected"] diff --git a/mcu/avrxmega-hal/src/lib.rs b/mcu/avrxmega-hal/src/lib.rs new file mode 100644 index 0000000000..d7840fa9b1 --- /dev/null +++ b/mcu/avrxmega-hal/src/lib.rs @@ -0,0 +1,55 @@ +#![no_std] + +//! `avrxmega-hal` +//! ============= +//! Common HAL (hardware abstraction layer) for AVR XMEGA microcontrollers. +//! +//! **Note**: This version of the documentation was built for +#![cfg_attr(feature = "attiny404", doc = "**ATtiny404**.")] +//! This means that only items which are available for this MCU are visible. If you are using +//! a different chip, try building the documentation locally with: +//! +//! ```text +//! cargo doc --features --open +//! ``` + +#[cfg(all( + not(feature = "device-selected"), + not(feature = "disable-device-selection-error") +))] +compile_error!( + "This crate requires you to specify your target chip as a feature. + + Please select one of the following + + * attiny404 + " +); + +/// Reexport of `attiny404` from `avr-device` +#[cfg(feature = "attiny404")] +pub use avr_device::attiny404 as pac; + +/// See [`avr_device::entry`](https://docs.rs/avr-device/latest/avr_device/attr.entry.html). +#[cfg(feature = "rt")] +pub use avr_device::entry; + +#[cfg(feature = "device-selected")] +pub use pac::Peripherals; + +pub use avr_hal_generic::clock; +pub use avr_hal_generic::delay; +pub use avr_hal_generic::prelude; + +#[cfg(feature = "device-selected")] +pub mod port; +#[cfg(feature = "device-selected")] +pub use port::Pins; + +#[cfg(feature = "attiny404")] +#[macro_export] +macro_rules! pins { + ($p:expr) => { + $crate::Pins::new($p.PORTA, $p.PORTB) + }; +} diff --git a/mcu/avrxmega-hal/src/port.rs b/mcu/avrxmega-hal/src/port.rs new file mode 100644 index 0000000000..3305e34f29 --- /dev/null +++ b/mcu/avrxmega-hal/src/port.rs @@ -0,0 +1,25 @@ +pub use avr_hal_generic::port::{mode, PinOps, PinMode}; + +#[cfg(feature = "attiny404")] +avr_hal_generic::impl_port_xmega! { + enum Ports { + PORTA: (crate::pac::PORTA), + PORTB: (crate::pac::PORTB), + } + + pub struct Pins { + pa0: PA0 = (crate::pac::PORTA, PORTA, 0, pin0ctrl), + pa1: PA1 = (crate::pac::PORTA, PORTA, 1, pin1ctrl), + pa2: PA2 = (crate::pac::PORTA, PORTA, 2, pin2ctrl), + pa3: PA3 = (crate::pac::PORTA, PORTA, 3, pin3ctrl), + pa4: PA4 = (crate::pac::PORTA, PORTA, 4, pin4ctrl), + pa5: PA5 = (crate::pac::PORTA, PORTA, 5, pin5ctrl), + pa6: PA6 = (crate::pac::PORTA, PORTA, 6, pin6ctrl), + pa7: PA7 = (crate::pac::PORTA, PORTA, 7, pin7ctrl), + + pb0: PB0 = (crate::pac::PORTB, PORTB, 0, pin0ctrl), + pb1: PB1 = (crate::pac::PORTB, PORTB, 1, pin1ctrl), + pb2: PB2 = (crate::pac::PORTB, PORTB, 2, pin2ctrl), + pb3: PB3 = (crate::pac::PORTB, PORTB, 3, pin3ctrl), + } +} \ No newline at end of file From 035e0e5f595e416094d201c1fe133b2c5962278f Mon Sep 17 00:00:00 2001 From: Adam Gausmann Date: Thu, 5 Jan 2023 19:36:44 -0600 Subject: [PATCH 2/3] Add attiny404 target spec --- avr-specs/avr-attiny404.json | 25 +++++++++++++++++++++++++ avr-specs/sync-from-upstream.py | 3 +++ 2 files changed, 28 insertions(+) create mode 100644 avr-specs/avr-attiny404.json diff --git a/avr-specs/avr-attiny404.json b/avr-specs/avr-attiny404.json new file mode 100644 index 0000000000..8fb077bca2 --- /dev/null +++ b/avr-specs/avr-attiny404.json @@ -0,0 +1,25 @@ +{ + "arch": "avr", + "atomic-cas": false, + "cpu": "attiny404", + "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8", + "eh-frame-header": false, + "exe-suffix": ".elf", + "executables": true, + "late-link-args": { + "gcc": [ + "-lgcc" + ] + }, + "linker": "avr-gcc", + "llvm-target": "avr-unknown-unknown", + "max-atomic-width": 8, + "no-default-libraries": false, + "pre-link-args": { + "gcc": [ + "-mmcu=attiny404" + ] + }, + "target-c-int-width": "16", + "target-pointer-width": "16" +} diff --git a/avr-specs/sync-from-upstream.py b/avr-specs/sync-from-upstream.py index 0b4ac01d55..0b8c27ac85 100755 --- a/avr-specs/sync-from-upstream.py +++ b/avr-specs/sync-from-upstream.py @@ -34,6 +34,9 @@ "attiny167": { "cpu": "attiny167", }, + "attiny404": { + "cpu": "attiny404", + }, "attiny2313": { "cpu": "attiny2313", }, From daf9a30dd39db502fe21df47d1e072892df269a2 Mon Sep 17 00:00:00 2001 From: Adam Gausmann Date: Thu, 5 Jan 2023 19:31:10 -0600 Subject: [PATCH 3/3] Add attiny404 to CI --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 528d64d084..5d08d3f67d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,6 +77,10 @@ jobs: name: attiny2313 spec: attiny2313 crate: attiny-hal + - type: mcu + name: attiny404 + spec: attiny404 + crate: avrxmega-hal runs-on: ubuntu-latest steps: - name: Checkout code