diff --git a/README.md b/README.md index 90fad5e83d7..f1e74709046 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ In addition to the PACs and HAL, there numerous **B**oard **S**upport **P**ackag | [atsame51n](https://docs.rs/atsame51n/) | [![Crates.io](https://img.shields.io/crates/v/atsame51n.svg)](https://crates.io/crates/atsame51n) | | | [atsamd51p](https://docs.rs/atsamd51p/) | [![Crates.io](https://img.shields.io/crates/v/atsamd51p.svg)](https://crates.io/crates/atsamd51p) | [Grand Central M4 Express][grand_central_m4], [Wio Terminal][wio_terminal] | | [atsame51g](https://docs.rs/atsame51g/) | [![Crates.io](https://img.shields.io/crates/v/atsame51g.svg)](https://crates.io/crates/atsame51g) | | -| [atsame51j](https://docs.rs/atsame51j/) | [![Crates.io](https://img.shields.io/crates/v/atsame51j.svg)](https://crates.io/crates/atsame51j) | | +| [atsame51j](https://docs.rs/atsame51j/) | [![Crates.io](https://img.shields.io/crates/v/atsame51j.svg)](https://crates.io/crates/atsame51j) | [Feather M4 CAN][feather_m4_can] | | [atsame51n](https://docs.rs/atsame51n/) | [![Crates.io](https://img.shields.io/crates/v/atsame51n.svg)](https://crates.io/crates/atsame51n) | | | [atsame53j](https://docs.rs/atsame53j/) | [![Crates.io](https://img.shields.io/crates/v/atsame53j.svg)](https://crates.io/crates/atsame53j) | | | [atsame53n](https://docs.rs/atsame53n/) | [![Crates.io](https://img.shields.io/crates/v/atsame53n.svg)](https://crates.io/crates/atsame53n) | | @@ -38,6 +38,7 @@ In addition to the PACs and HAL, there numerous **B**oard **S**upport **P**ackag [edgebadge]: https://github.com/atsamd-rs/atsamd/tree/master/boards/edgebadge [feather_m0]: https://github.com/atsamd-rs/atsamd/tree/master/boards/feather_m0/ [feather_m4]: https://github.com/atsamd-rs/atsamd/tree/master/boards/feather_m4/ +[feather_m4_can]: https://github.com/atsamd-rs/atsamd/tree/master/boards/feather_m4_can/ [gemma_m0]: https://github.com/atsamd-rs/atsamd/tree/master/boards/gemma_m0/ [grand_central_m4]: https://github.com/atsamd-rs/atsamd/tree/master/boards/grand_central_m4/ [itsybitsy_m0]: https://github.com/atsamd-rs/atsamd/tree/master/boards/itsybitsy_m0/ diff --git a/boards/feather_m4_can/.cargo/config b/boards/feather_m4_can/.cargo/config new file mode 100644 index 00000000000..4b9346206cf --- /dev/null +++ b/boards/feather_m4_can/.cargo/config @@ -0,0 +1,14 @@ +# vim:ft=toml: +[target.thumbv7em-none-eabihf] +runner = 'arm-none-eabi-gdb' + +[build] +target = "thumbv7em-none-eabihf" +rustflags = [ + + # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x + # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 + "-C", "link-arg=--nmagic", + + "-C", "link-arg=-Tlink.x", +] diff --git a/boards/feather_m4_can/CHANGELOG.md b/boards/feather_m4_can/CHANGELOG.md new file mode 100644 index 00000000000..19bda6f6f2c --- /dev/null +++ b/boards/feather_m4_can/CHANGELOG.md @@ -0,0 +1,5 @@ +# Unreleased + +# v0.0.1 + +- Initial version diff --git a/boards/feather_m4_can/Cargo.toml b/boards/feather_m4_can/Cargo.toml new file mode 100644 index 00000000000..0838c1c9e5c --- /dev/null +++ b/boards/feather_m4_can/Cargo.toml @@ -0,0 +1,70 @@ +[package] +name = "feather_m4_can" +version = "0.0.1" +edition = "2021" +authors = ["Jeremy Boynes "] +description = "Board Support crate for the Adafruit Feather M4 CAN Express" +keywords = ["no-std", "arm", "cortex-m", "embedded-hal"] +categories = ["embedded", "hardware-support", "no-std"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/atsamd-rs/atsamd" +readme = "README.md" +documentation = "https://atsamd-rs.github.io/atsamd/atsame51j/feather_m4_can/" + +# for cargo flash +[package.metadata] +chip = "ATSAME51J19A" + +[dependencies.cortex-m-rt] +version = "0.7" +optional = true + +[dependencies.atsamd-hal] +path = "../../hal" +version = "0.15.1" +default-features = false + +[dependencies.usb-device] +version = "0.2" +optional = true + +[dev-dependencies] +cortex-m = "0.7" +usbd-serial = "0.1" +cortex-m-rtic = "0.6.0-rc.2" +panic-halt = "0.2" +panic-semihosting = "0.5" +smart-leds = "0.3" +ws2812-timer-delay = "0.3" +heapless = "0.7" + +[features] +# ask the HAL to enable atsame51j support +default = ["rt", "atsamd-hal/same51j", "atsamd-hal/unproven"] +rt = ["cortex-m-rt", "atsamd-hal/same51j-rt"] +unproven = ["atsamd-hal/unproven"] +usb = ["atsamd-hal/usb", "usb-device"] +dma = ["atsamd-hal/dma", "unproven"] +max-channels = ["dma", "atsamd-hal/dma"] + + +[profile.dev] +incremental = false +codegen-units = 1 +debug = true +lto = true + +[profile.release] +debug = true +lto = true +opt-level = "s" + +[[example]] +name = "blinky_basic" + +[[example]] +name = "neopixel_rainbow" + +[[example]] +name = "usb_echo" +required-features = ["usb"] diff --git a/boards/feather_m4_can/README.md b/boards/feather_m4_can/README.md new file mode 100644 index 00000000000..a32bb36907f --- /dev/null +++ b/boards/feather_m4_can/README.md @@ -0,0 +1,26 @@ +# Adafruit Feather M4 CAN Express Board Support Crate + +This crate provides a type-safe API for working with the +[Adafruit Feather M4 CAN Express board](https://www.adafruit.com/product/4759). + +## Prerequisites +* Install the cross compile toolchain `rustup target add thumbv7em-none-eabihf` +* Install [cargo-hf2 the hf2 bootloader flasher tool](https://crates.io/crates/cargo-hf2) however your platform requires + +## Uploading an example +Check out the repository for examples: + +https://github.com/atsamd-rs/atsamd/tree/master/boards/feather_m4_can/examples + +* Be in this directory `cd boards/feather_m4_can` +* Put your device in bootloader mode usually by hitting the reset button twice. +* Build and upload in one step +``` +$ cargo hf2 --release --example blinky_basic + Finished release [optimized + debuginfo] target(s) in 15.80s + Searching for a connected device with known vid/pid pair. + Trying Ok(Some("Adafruit Industries")) Ok(Some("Feather M4 CAN Express")) + Flashing ".../atsamd-rs/atsamd/boards/feather_m4_can/target/thumbv7em-none-eabihf/release/examples/blinky_basic" + Finished in 0.104s +$ +``` diff --git a/boards/feather_m4_can/build.rs b/boards/feather_m4_can/build.rs new file mode 100644 index 00000000000..4bed4688f2c --- /dev/null +++ b/boards/feather_m4_can/build.rs @@ -0,0 +1,16 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; +fn main() { + if env::var_os("CARGO_FEATURE_RT").is_some() { + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=memory.x"); + } + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/boards/feather_m4_can/examples/blinky_basic.rs b/boards/feather_m4_can/examples/blinky_basic.rs new file mode 100644 index 00000000000..26d182ed130 --- /dev/null +++ b/boards/feather_m4_can/examples/blinky_basic.rs @@ -0,0 +1,37 @@ +#![no_std] +#![no_main] + +use feather_m4_can as bsp; +#[cfg(not(feature = "use_semihosting"))] +use panic_halt as _; +#[cfg(feature = "use_semihosting")] +use panic_semihosting as _; + +use bsp::entry; +use bsp::hal; +use hal::clock::GenericClockController; +use hal::delay::Delay; +use hal::pac::{CorePeripherals, Peripherals}; +use hal::prelude::*; + +#[entry] +fn main() -> ! { + let mut peripherals = Peripherals::take().unwrap(); + let core = CorePeripherals::take().unwrap(); + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.GCLK, + &mut peripherals.MCLK, + &mut peripherals.OSC32KCTRL, + &mut peripherals.OSCCTRL, + &mut peripherals.NVMCTRL, + ); + let pins = bsp::Pins::new(peripherals.PORT); + let mut red_led = pins.d13.into_push_pull_output(); + let mut delay = Delay::new(core.SYST, &mut clocks); + loop { + delay.delay_ms(2000u16); + red_led.set_high().unwrap(); + delay.delay_ms(2000u16); + red_led.set_low().unwrap(); + } +} diff --git a/boards/feather_m4_can/examples/neopixel_rainbow.rs b/boards/feather_m4_can/examples/neopixel_rainbow.rs new file mode 100644 index 00000000000..2ad925e2cfa --- /dev/null +++ b/boards/feather_m4_can/examples/neopixel_rainbow.rs @@ -0,0 +1,72 @@ +#![no_std] +#![no_main] + +// Neopixel Rainbow +// This only functions when the --release version is compiled. Using the debug +// version leads to slow pulse durations which results in a straight white LED +// output. +// +// // Needs to be compiled with --release for the timing to be correct + +use bsp::hal; +use feather_m4_can as bsp; + +#[cfg(not(feature = "use_semihosting"))] +use panic_halt as _; +#[cfg(feature = "use_semihosting")] +use panic_semihosting as _; + +use bsp::entry; +use hal::clock::GenericClockController; +use hal::delay::Delay; +use hal::pac::{CorePeripherals, Peripherals}; +use hal::prelude::*; +use hal::timer::*; + +use smart_leds::{ + hsv::{hsv2rgb, Hsv}, + SmartLedsWrite, +}; +use ws2812_timer_delay::Ws2812; + +#[entry] +fn main() -> ! { + let mut peripherals = Peripherals::take().unwrap(); + let core = CorePeripherals::take().unwrap(); + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.GCLK, + &mut peripherals.MCLK, + &mut peripherals.OSC32KCTRL, + &mut peripherals.OSCCTRL, + &mut peripherals.NVMCTRL, + ); + let pins = bsp::Pins::new(peripherals.PORT); + let mut delay = Delay::new(core.SYST, &mut clocks); + + let gclk0 = clocks.gclk0(); + let timer_clock = clocks.tc2_tc3(&gclk0).unwrap(); + let mut timer = TimerCounter::tc3_(&timer_clock, peripherals.TC3, &mut peripherals.MCLK); + timer.start(3.mhz()); + + // Power up the Neopixel just in case it wasn't done by the bootloader. + let mut neopixel_power = pins.neopixel_power.into_push_pull_output(); + neopixel_power.set_drive_strength(true); + neopixel_power.set_high().unwrap(); + + let neopixel_pin = pins.neopixel.into_push_pull_output(); + let mut neopixel = Ws2812::new(timer, neopixel_pin); + + // Loop through all of the available hue values (colors) to make a + // rainbow effect from the onboard neopixel + loop { + for j in 0..255u8 { + let colors = [hsv2rgb(Hsv { + hue: j, + sat: 255, + val: 2, + })]; + neopixel.write(colors.iter().cloned()).unwrap(); + delay.delay_ms(5u8); + } + } +} diff --git a/boards/feather_m4_can/examples/usb_echo.rs b/boards/feather_m4_can/examples/usb_echo.rs new file mode 100644 index 00000000000..eece8513a03 --- /dev/null +++ b/boards/feather_m4_can/examples/usb_echo.rs @@ -0,0 +1,114 @@ +#![no_std] +#![no_main] + +use bsp::hal; +use feather_m4_can as bsp; + +#[cfg(not(feature = "use_semihosting"))] +use panic_halt as _; +#[cfg(feature = "use_semihosting")] +use panic_semihosting as _; + +use bsp::entry; +use hal::clock::GenericClockController; +use hal::pac::{interrupt, CorePeripherals, Peripherals}; +use hal::prelude::*; +use hal::usb::UsbBus; + +use usb_device::bus::UsbBusAllocator; +use usb_device::prelude::*; +use usbd_serial::{SerialPort, USB_CLASS_CDC}; + +use cortex_m::asm::delay as cycle_delay; +use cortex_m::peripheral::NVIC; + +#[entry] +fn main() -> ! { + let mut peripherals = Peripherals::take().unwrap(); + let mut core = CorePeripherals::take().unwrap(); + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.GCLK, + &mut peripherals.MCLK, + &mut peripherals.OSC32KCTRL, + &mut peripherals.OSCCTRL, + &mut peripherals.NVMCTRL, + ); + let pins = bsp::Pins::new(peripherals.PORT); + let mut red_led: bsp::RedLed = pins.d13.into(); + + let bus_allocator = unsafe { + USB_ALLOCATOR = Some(bsp::usb_allocator( + pins.usb_dm, + pins.usb_dp, + peripherals.USB, + &mut clocks, + &mut peripherals.MCLK, + )); + USB_ALLOCATOR.as_ref().unwrap() + }; + + unsafe { + USB_SERIAL = Some(SerialPort::new(bus_allocator)); + USB_BUS = Some( + UsbDeviceBuilder::new(bus_allocator, UsbVidPid(0x16c0, 0x27dd)) + .manufacturer("Fake company") + .product("Serial port") + .serial_number("TEST") + .device_class(USB_CLASS_CDC) + .build(), + ); + } + + unsafe { + core.NVIC.set_priority(interrupt::USB_OTHER, 1); + core.NVIC.set_priority(interrupt::USB_TRCPT0, 1); + core.NVIC.set_priority(interrupt::USB_TRCPT1, 1); + NVIC::unmask(interrupt::USB_OTHER); + NVIC::unmask(interrupt::USB_TRCPT0); + NVIC::unmask(interrupt::USB_TRCPT1); + } + + loop { + cycle_delay(5 * 1024 * 1024); + red_led.toggle().unwrap(); + } +} + +static mut USB_ALLOCATOR: Option> = None; +static mut USB_BUS: Option> = None; +static mut USB_SERIAL: Option> = None; + +fn poll_usb() { + unsafe { + if let Some(usb_dev) = USB_BUS.as_mut() { + if let Some(serial) = USB_SERIAL.as_mut() { + usb_dev.poll(&mut [serial]); + let mut buf = [0u8; 64]; + + if let Ok(count) = serial.read(&mut buf) { + for (i, c) in buf.iter().enumerate() { + if i >= count { + break; + } + serial.write(&[*c]).unwrap(); + } + }; + }; + }; + }; +} + +#[interrupt] +fn USB_OTHER() { + poll_usb(); +} + +#[interrupt] +fn USB_TRCPT0() { + poll_usb(); +} + +#[interrupt] +fn USB_TRCPT1() { + poll_usb(); +} diff --git a/boards/feather_m4_can/memory.x b/boards/feather_m4_can/memory.x new file mode 100644 index 00000000000..073403cecc8 --- /dev/null +++ b/boards/feather_m4_can/memory.x @@ -0,0 +1,8 @@ +MEMORY +{ + /* Leave 16k for the default bootloader on the Feather M4 */ + FLASH (rx) : ORIGIN = 0x00000000 + 16K, LENGTH = 512K - 16K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K +} +_stack_start = ORIGIN(RAM) + LENGTH(RAM); + diff --git a/boards/feather_m4_can/src/lib.rs b/boards/feather_m4_can/src/lib.rs new file mode 100644 index 00000000000..4faa34a2494 --- /dev/null +++ b/boards/feather_m4_can/src/lib.rs @@ -0,0 +1,294 @@ +#![no_std] +#![deny(missing_docs)] + +//! Board support crate for Adafruit's Feather M4 CAN Express, +//! an ATSAME51-based board with CAN, in a Feather form factor. + +#[cfg(feature = "rt")] +pub use cortex_m_rt::entry; + +pub use atsamd_hal as hal; +pub use hal::ehal; +pub use hal::pac; + +use hal::clock::GenericClockController; +use hal::sercom::{ + i2c, spi, + uart::{self, BaudMode, Oversampling}, + IoSet1, UndocIoSet1, +}; +use hal::time::Hertz; + +#[cfg(feature = "usb")] +use hal::usb::usb_device::bus::UsbBusAllocator; +#[cfg(feature = "usb")] +pub use hal::usb::UsbBus; + +hal::bsp_peripherals!( + SERCOM1 { SpiSercom } + SERCOM2 { I2cSercom } + SERCOM5 { UartSercom } +); + +hal::bsp_pins!( + /// Headers + PA02 { + /// Analog pin 0. Can act as a true analog output + /// as it has a DAC (which is not currently supported + /// by this hal) as well as input. + name: a0, + } + PA05 { + /// Analog Pin 1 + name: a1, + } + PB08 { + /// Analog Pin 2 + name: a2, + } + PB09 { + /// Analog Pin 3 + name: a3, + } + PA04 { + /// Analog Pin 4 + name: a4, + } + PA06 { + /// Analog Pin 5 + name: a5, + } + PA17 { + /// SCK + name: sck, + aliases: { + AlternateC: Sclk + } + } + PB23 { + /// MOSI + name: mosi, + aliases: { + AlternateC: Mosi + } + } + PB22 { + /// MISO + name: miso, + aliases: { + AlternateC: Miso + } + } + PB17 { + /// Pin 0, UART rx + name: d0, + aliases: { + AlternateC: UartRx + } + } + PB16 { + /// Pin 1, UART tx + name: d1, + aliases: { + AlternateC: UartTx + } + } + PA14 { + /// Pin 4, PWM capable + name: d4, + } + + PA12 { + /// SDA + name: sda, + aliases: { + AlternateC: Sda + } + } + PA13 { + /// SCL + name: scl, + aliases: { + AlternateC: Scl + } + } + PA16 { + /// Pin 5, PWM capable + name: d5, + } + PA18 { + /// Pin 6, PWM capable + name: d6, + } + PA19 { + /// Pin 9, PWM capable. Also analog input (A7) + name: d9, + } + PA20 { + /// Pin 10, PWM capable + name: d10, + } + PA21 { + /// Pin 11, PWM capable + name: d11, + } + PA22 { + /// Pin 12, PWM capable + name: d12, + } + PA23 { + /// Pin 13, which is also attached to the red LED. PWM capable. + name: d13, + aliases: { + PushPullOutput: RedLed, + AlternateE: RedLedPwm + } + } + + PA03 { + /// AREF pin - has 1uF capacitor to ground + name: aref + } + PB00 { + /// Analog Vdiv (1/2 resistor divider for monitoring the battery) + name: battery, + } + + /// Onboard Neopixel + PB02 { + /// Neopixel Data + name: neopixel, + } + PB03 { + /// Neopixel Power + name: neopixel_power, + } + + /// USB pins. + PA24 { + /// The USB D- pad + name: usb_dm, + aliases: { + AlternateH: UsbDm + } + } + PA25 { + /// The USB D+ pad + name: usb_dp, + aliases: { + AlternateH: UsbDp + } + } + + // TODO: Define pins for QSPI Flash + // TODO: Define pins for on-board CAN +); + +/// SPI pads for the labelled SPI peripheral +/// +/// You can use these pads with other, user-defined [`spi::Config`]urations. +pub type SpiPads = spi::Pads; + +/// SPI master for the labelled SPI peripheral +/// +/// This type implements [`FullDuplex`](ehal::spi::FullDuplex). +pub type Spi = spi::Spi, spi::Duplex>; + +/// Convenience for setting up the labelled SPI peripheral. +/// This powers up SERCOM1 and configures it for use as an +/// SPI Master in SPI Mode 0. +pub fn spi_master( + clocks: &mut GenericClockController, + baud: impl Into, + sercom: SpiSercom, + mclk: &mut pac::MCLK, + sclk: impl Into, + mosi: impl Into, + miso: impl Into, +) -> Spi { + let gclk0 = clocks.gclk0(); + let clock = clocks.sercom1_core(&gclk0).unwrap(); + let freq = clock.freq(); + let (miso, mosi, sclk) = (miso.into(), mosi.into(), sclk.into()); + let pads = spi::Pads::default().data_in(miso).data_out(mosi).sclk(sclk); + spi::Config::new(mclk, sercom, pads, freq) + .baud(baud) + .spi_mode(spi::MODE_0) + .enable() +} + +/// I2C pads for the labelled I2C peripheral +/// +/// You can use these pads with other, user-defined [`i2c::Config`]urations. +pub type I2cPads = i2c::Pads; + +/// I2C master for the labelled I2C peripheral +/// +/// This type implements [`Read`](ehal::blocking::i2c::Read), +/// [`Write`](ehal::blocking::i2c::Write) and +/// [`WriteRead`](ehal::blocking::i2c::WriteRead). +pub type I2c = i2c::I2c>; + +/// Convenience for setting up the labelled SDA, SCL pins to +/// operate as an I2C master running at the specified frequency. +pub fn i2c_master( + clocks: &mut GenericClockController, + baud: impl Into, + sercom: I2cSercom, + mclk: &mut pac::MCLK, + sda: impl Into, + scl: impl Into, +) -> I2c { + let gclk0 = clocks.gclk0(); + let clock = &clocks.sercom2_core(&gclk0).unwrap(); + let freq = clock.freq(); + let baud = baud.into(); + let pads = i2c::Pads::new(sda.into(), scl.into()); + i2c::Config::new(mclk, sercom, pads, freq) + .baud(baud) + .enable() +} + +/// UART pads for the labelled RX & TX pins +pub type UartPads = uart::Pads; + +/// UART device for the labelled RX & TX pins +pub type Uart = uart::Uart, uart::Duplex>; + +/// Convenience for setting up the labelled RX, TX pins to +/// operate as a UART device running at the specified baud. +pub fn uart( + clocks: &mut GenericClockController, + baud: impl Into, + sercom: UartSercom, + mclk: &mut pac::MCLK, + rx: impl Into, + tx: impl Into, +) -> Uart { + let gclk0 = clocks.gclk0(); + + let clock = &clocks.sercom5_core(&gclk0).unwrap(); + let baud = baud.into(); + let pads = uart::Pads::default().rx(rx.into()).tx(tx.into()); + uart::Config::new(mclk, sercom, pads, clock.freq()) + .baud(baud, BaudMode::Fractional(Oversampling::Bits16)) + .enable() +} + +#[cfg(feature = "usb")] +/// Convenience function for setting up USB +pub fn usb_allocator( + dm: impl Into, + dp: impl Into, + usb: pac::USB, + clocks: &mut GenericClockController, + mclk: &mut pac::MCLK, +) -> UsbBusAllocator { + use pac::gclk::{genctrl::SRC_A, pchctrl::GEN_A}; + + clocks.configure_gclk_divider_and_source(GEN_A::GCLK2, 1, SRC_A::DFLL, false); + let usb_gclk = clocks.get_gclk(GEN_A::GCLK2).unwrap(); + let usb_clock = &clocks.usb(&usb_gclk).unwrap(); + let (dm, dp) = (dm.into(), dp.into()); + UsbBusAllocator::new(UsbBus::new(usb_clock, mclk, dm, dp, usb)) +} diff --git a/crates.json b/crates.json index 7a4ca8603ab..3f4596aafca 100644 --- a/crates.json +++ b/crates.json @@ -45,6 +45,11 @@ "build": "cargo build --examples --all-features", "target": "thumbv7em-none-eabihf" }, + "feather_m4_can": { + "tier": 2, + "build": "cargo build --examples --all-features", + "target": "thumbv7em-none-eabihf" + }, "gemma_m0": { "tier": 2, "build": "cargo build --examples --all-features",