From 55b2f0ab695e9be110dc784e755b2338abb07213 Mon Sep 17 00:00:00 2001 From: Daniel Mason Date: Sat, 9 Oct 2021 14:31:52 +0100 Subject: [PATCH] Add Neo Trinkey BSP (#471) --- boards/neo_trinkey/.cargo/config | 15 +++ boards/neo_trinkey/CHANGELOG.md | 18 ++++ boards/neo_trinkey/Cargo.toml | 47 ++++++++ boards/neo_trinkey/README.md | 100 ++++++++++++++++++ boards/neo_trinkey/build.rs | 16 +++ boards/neo_trinkey/examples/blinky_basic.rs | 56 ++++++++++ boards/neo_trinkey/examples/blinky_rainbow.rs | 68 ++++++++++++ boards/neo_trinkey/examples/usb_ack.rs | 99 +++++++++++++++++ boards/neo_trinkey/memory.x | 8 ++ boards/neo_trinkey/src/lib.rs | 88 +++++++++++++++ crates.json | 4 + 11 files changed, 519 insertions(+) create mode 100644 boards/neo_trinkey/.cargo/config create mode 100644 boards/neo_trinkey/CHANGELOG.md create mode 100644 boards/neo_trinkey/Cargo.toml create mode 100644 boards/neo_trinkey/README.md create mode 100644 boards/neo_trinkey/build.rs create mode 100644 boards/neo_trinkey/examples/blinky_basic.rs create mode 100644 boards/neo_trinkey/examples/blinky_rainbow.rs create mode 100644 boards/neo_trinkey/examples/usb_ack.rs create mode 100644 boards/neo_trinkey/memory.x create mode 100644 boards/neo_trinkey/src/lib.rs diff --git a/boards/neo_trinkey/.cargo/config b/boards/neo_trinkey/.cargo/config new file mode 100644 index 000000000000..05d096540fd9 --- /dev/null +++ b/boards/neo_trinkey/.cargo/config @@ -0,0 +1,15 @@ +# samd21 is a Cortex-M0 and thus thumbv6m + +[build] +target = "thumbv6m-none-eabi" + +[target.thumbv6m-none-eabi] +runner = 'arm-none-eabi-gdb' +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/neo_trinkey/CHANGELOG.md b/boards/neo_trinkey/CHANGELOG.md new file mode 100644 index 000000000000..b31e7dd450f7 --- /dev/null +++ b/boards/neo_trinkey/CHANGELOG.md @@ -0,0 +1,18 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres +to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.1.0] - 2021-10-07 + +### Added +- pin map +- usb_allocator helper function +- Examples: + - blinky_basic + - blinky_rainbow + - usb_ack diff --git a/boards/neo_trinkey/Cargo.toml b/boards/neo_trinkey/Cargo.toml new file mode 100644 index 000000000000..f3205c92470f --- /dev/null +++ b/boards/neo_trinkey/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "neo_trinkey" +version = "0.1.0" +authors = ["Daniel Mason "] +description = "Board Support crate for the Adafruit Neo Trinkey" +keywords = ["no-std", "arm", "cortex-m", "embedded-hal"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/atsamd-rs/atsamd" +readme = "README.md" +edition = "2018" + +[dependencies] +cortex-m = "0.6" +embedded-hal = "0.2.3" +cortex-m-rt = { version = "0.6.12", optional = true } +usb-device = { version = "0.2", optional = true } +usbd-serial = { version = "0.1", optional = true } +smart-leds = { version = "0.3.0", optional = true } +ws2812-timer-delay = { version = "0.3.0", features = ["slow"], optional = true } + +[dependencies.atsamd-hal] +version = "0.13" +default-features = false + +[dev-dependencies] +panic-halt = "0.2" + +[features] +# ask the HAL to enable atsamd21e support +default = ["rt", "atsamd-hal/samd21e"] +leds = ["ws2812-timer-delay", "smart-leds"] +rt = ["cortex-m-rt", "atsamd-hal/samd21e-rt"] +unproven = ["atsamd-hal/unproven"] +use_semihosting = [] +usb = ["atsamd-hal/usb", "usb-device", "usbd-serial"] + +[[example]] +name = "blinky_basic" +required-features = ["leds"] + +[[example]] +name = "blinky_rainbow" +required-features = ["leds"] + +[[example]] +name = "usb_ack" +required-features = ["usb"] diff --git a/boards/neo_trinkey/README.md b/boards/neo_trinkey/README.md new file mode 100644 index 000000000000..a91d7965b62e --- /dev/null +++ b/boards/neo_trinkey/README.md @@ -0,0 +1,100 @@ +# Adafruit Neo Trinkey Board Support Crate + +This crate provides a type-safe API for working with the [Adafruit Neo Trinkey +board](https://www.adafruit.com/product/4870). + +## Prerequisites +* Install the cross compile toolchain `rustup target add thumbv6m-none-eabi` +* 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/neo_trinkey/examples + +* Be in this directory `cd boards/neo_trinkey` +* Put your device in bootloader mode usually by hitting the reset button twice. +* Build and upload in one step +```bash +$ cargo hf2 --release --example --features +``` + +You should see the following output +```text +Finished release [optimized] target(s) in 5.55s +Trying Ok(Some("Adafruit Industries")) Ok(Some("NeoPixel Trinkey M0")) +Flashing "/Users/danielmason/projects/rust/atsamd/boards/neo_trinkey/target/thumbv6m-none-eabi/release/examples/blinky_basic" +Finished in 0.051s +``` +Note: If hf2 can not find your Neo Trinkey, check that you have the latest version of cargo-hf2. + +If it still doesn't work you can add the Product ID (pid) and Vendor ID (vid) which are usually `0x00ef` and `0x239a` +respectively. + +```bash +$ cargo hf2 --release --example --features --pid 0x00ef --vid 0x239a +``` + +If this _still_ doesn't work, check the USB device in your system settings in case your pid and vid are different. + +## Examples + +### Blinky basic + +```bash +$ cargo hf2 --release --example blinky_basic --features leds +``` + +Once the Neo Trinkey has restarted, you will see the 4 leds flash in unison. Each led will be a different color (pink, +cyan, yellow and white). + +**Warning** even though the lights are turned down very low, they are still very bright. + +### Blinky rainbow + +```bash +$ cargo hf2 --release --example blinky_rainbow --features leds +``` + +A slightly more satisfying version of blinky where the lights will cycle through the color spectrum. + +**Warning** even though the lights are turned down very low, they are still very bright. + +### USB ack + +```bash +$ cargo hf2 --release --example usb_ack --features usb +``` + +Once the device has reset, all the lights will be off. You will then need to find the USB device on your machine. + +Usually this is located in `/dev/cu.usbmodemTRINKEY_ACK1`. though if you have multiple trinkeys plugged in and running +this example, the number at the end may change. + +You can then send the USB device bytes. Each time the device receives data, it will respond with "Received: X" where X +is the data that it received. To test this in a variety of ways but the easiest is probably with screen. + +Connect to the device like this (9600 is the baud rate) + +```bash +$ screen /dev/cu.usbmodemTRINKEY_ECHO1 9600 +``` + +You can then press keys and you should get a response Eg: + +```text +Received: h +Received: e +Received: l +Received: l +Received: o +Received: +Received: w +Received: o +Received: r +Received: l +Received: d +``` + +To quit screen, use `ctrl-a` followed by `crtl-\` then `y` + diff --git a/boards/neo_trinkey/build.rs b/boards/neo_trinkey/build.rs new file mode 100644 index 000000000000..4bed4688f2c0 --- /dev/null +++ b/boards/neo_trinkey/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/neo_trinkey/examples/blinky_basic.rs b/boards/neo_trinkey/examples/blinky_basic.rs new file mode 100644 index 000000000000..1aa804de6ed3 --- /dev/null +++ b/boards/neo_trinkey/examples/blinky_basic.rs @@ -0,0 +1,56 @@ +#![no_std] +#![no_main] +use panic_halt as _; + +use neo_trinkey as bsp; + +use bsp::entry; +use bsp::hal; + +use hal::clock::GenericClockController; +use hal::delay::Delay; +use hal::pac::{CorePeripherals, Peripherals}; +use hal::prelude::*; +use hal::timer::TimerCounter; + +use smart_leds::{hsv::RGB8, 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_internal_32kosc( + peripherals.GCLK, + &mut peripherals.PM, + &mut peripherals.SYSCTRL, + &mut peripherals.NVMCTRL, + ); + + let pins = bsp::Pins::new(peripherals.PORT); + + let gclk0 = clocks.gclk0(); + let timer_clock = clocks.tcc2_tc3(&gclk0).unwrap(); + let mut timer = TimerCounter::tc3_(&timer_clock, peripherals.TC3, &mut peripherals.PM); + timer.start(3.mhz()); + let neo_pixel = pins.neo_pixel.into_push_pull_output(); + let mut ws2812 = Ws2812::new(timer, neo_pixel); + + let mut delay = Delay::new(core.SYST, &mut clocks); + + const NUM_LEDS: usize = 4; + let off = [RGB8::default(); NUM_LEDS]; + let on = [ + RGB8::new(5, 5, 0), + RGB8::new(0, 5, 5), + RGB8::new(5, 0, 5), + RGB8::new(2, 2, 2), + ]; + + loop { + ws2812.write(off.iter().cloned()).unwrap(); + delay.delay_ms(500u16); + ws2812.write(on.iter().cloned()).unwrap(); + delay.delay_ms(500u16); + } +} diff --git a/boards/neo_trinkey/examples/blinky_rainbow.rs b/boards/neo_trinkey/examples/blinky_rainbow.rs new file mode 100644 index 000000000000..66bc312ce202 --- /dev/null +++ b/boards/neo_trinkey/examples/blinky_rainbow.rs @@ -0,0 +1,68 @@ +#![no_std] +#![no_main] +use panic_halt as _; + +use neo_trinkey as bsp; + +use bsp::entry; +use bsp::hal; + +use hal::clock::GenericClockController; +use hal::delay::Delay; +use hal::pac::{CorePeripherals, Peripherals}; +use hal::prelude::*; +use hal::timer::TimerCounter; + +use smart_leds::{brightness, hsv::RGB8, 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_internal_32kosc( + peripherals.GCLK, + &mut peripherals.PM, + &mut peripherals.SYSCTRL, + &mut peripherals.NVMCTRL, + ); + + let pins = bsp::Pins::new(peripherals.PORT); + + let gclk0 = clocks.gclk0(); + let timer_clock = clocks.tcc2_tc3(&gclk0).unwrap(); + let mut timer = TimerCounter::tc3_(&timer_clock, peripherals.TC3, &mut peripherals.PM); + timer.start(3.mhz()); + let neo_pixel = pins.neo_pixel.into_push_pull_output(); + let mut ws2812 = Ws2812::new(timer, neo_pixel); + + let mut delay = Delay::new(core.SYST, &mut clocks); + + const NUM_LEDS: usize = 4; + let mut data = [RGB8::default(); NUM_LEDS]; + + loop { + for j in 0..(256 * 5) { + for i in 0..NUM_LEDS { + data[i] = wheel((((i * 256) as u16 / NUM_LEDS as u16 + j as u16) & 255) as u8); + } + ws2812.write(brightness(data.iter().cloned(), 32)).unwrap(); + delay.delay_ms(5u8); + } + } +} + +/// Input a value 0 to 255 to get a color value +/// The colours are a transition r - g - b - back to r. +fn wheel(mut wheel_pos: u8) -> RGB8 { + wheel_pos = 255 - wheel_pos; + if wheel_pos < 85 { + return (255 - wheel_pos * 3, 0, wheel_pos * 3).into(); + } + if wheel_pos < 170 { + wheel_pos -= 85; + return (0, wheel_pos * 3, 255 - wheel_pos * 3).into(); + } + wheel_pos -= 170; + (wheel_pos * 3, 255 - wheel_pos * 3, 0).into() +} diff --git a/boards/neo_trinkey/examples/usb_ack.rs b/boards/neo_trinkey/examples/usb_ack.rs new file mode 100644 index 000000000000..2427ee577b6d --- /dev/null +++ b/boards/neo_trinkey/examples/usb_ack.rs @@ -0,0 +1,99 @@ +#![no_std] +#![no_main] +use panic_halt as _; + +use cortex_m::asm::delay as cycle_delay; +use cortex_m::peripheral::NVIC; +use usb_device::bus::UsbBusAllocator; +use usb_device::prelude::*; +use usbd_serial::{SerialPort, USB_CLASS_CDC}; + +use neo_trinkey as bsp; + +use bsp::entry; +use bsp::hal; +use bsp::pac; + +use hal::clock::GenericClockController; +use hal::usb::UsbBus; +use pac::{interrupt, CorePeripherals, Peripherals}; + +#[entry] +fn main() -> ! { + let mut peripherals = Peripherals::take().unwrap(); + let mut core = CorePeripherals::take().unwrap(); + let mut clocks = GenericClockController::with_internal_32kosc( + peripherals.GCLK, + &mut peripherals.PM, + &mut peripherals.SYSCTRL, + &mut peripherals.NVMCTRL, + ); + let pins = bsp::Pins::new(peripherals.PORT); + + let bus_allocator = unsafe { + USB_ALLOCATOR = Some(bsp::usb_allocator( + peripherals.USB, + &mut clocks, + &mut peripherals.PM, + pins.usb_dm, + pins.usb_dp, + )); + 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("TRINKEY_ACK") + .device_class(USB_CLASS_CDC) + .build(), + ); + } + + unsafe { + core.NVIC.set_priority(interrupt::USB, 1); + NVIC::unmask(interrupt::USB); + } + + loop { + cycle_delay(15 * 1024 * 1024); + } +} + +static mut USB_ALLOCATOR: Option> = None; +static mut USB_BUS: Option> = None; +static mut USB_SERIAL: Option> = None; + +fn poll_usb() { + unsafe { + USB_BUS.as_mut().map(|usb_dev| { + USB_SERIAL.as_mut().map(|serial| { + 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("Received: ".as_bytes()).ok(); + serial.write(&[c.clone()]).ok(); + serial.write("\r\n".as_bytes()).ok(); + } + }; + }); + }); + }; +} + +#[interrupt] +fn USB() { + // Note: USB is the name of the interrupt, you can not attach the #[interrupt] + // tag to poll_usb. Although you could add the contents of poll_usb into + // this function, separating them allows you to add more functions to run on + // the USB interrupt in the future. + poll_usb(); +} diff --git a/boards/neo_trinkey/memory.x b/boards/neo_trinkey/memory.x new file mode 100644 index 000000000000..6f7f80898daa --- /dev/null +++ b/boards/neo_trinkey/memory.x @@ -0,0 +1,8 @@ +MEMORY +{ + /* Leave 8k for the default bootloader on the Neo Trinkey */ + FLASH (rx) : ORIGIN = 0x00000000 + 8K, LENGTH = 256K - 8K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 32K +} +_stack_start = ORIGIN(RAM) + LENGTH(RAM); + diff --git a/boards/neo_trinkey/src/lib.rs b/boards/neo_trinkey/src/lib.rs new file mode 100644 index 000000000000..025f51302b7a --- /dev/null +++ b/boards/neo_trinkey/src/lib.rs @@ -0,0 +1,88 @@ +#![no_std] + +#[cfg(feature = "rt")] +pub use cortex_m_rt::entry; + +pub use atsamd_hal as hal; +pub use embedded_hal as ehal; +pub use hal::pac; + +#[cfg(feature = "usb")] +pub use hal::usb::{usb_device::bus::UsbBusAllocator, UsbBus}; + +hal::bsp_pins!( + PA03 { + name: touch1, + aliases: { + AlternateB: Touch1 + } + }, + + PA05 { + name: neo_pixel, + aliases: { + AlternateB: NeoPixel + } + }, + + PA07 { + name: touch2, + aliases: { AlternateB: Touch2 } + }, + + PA24 { + /// The USB D- pad + name: usb_dm + aliases: { + AlternateG: UsbDm + } + }, + + PA25 { + /// The USB D+ pad + name: usb_dp + aliases: { + AlternateG: UsbDp + } + } +); + +/// Convenience method for getting the USB Bus Allocator. +/// +/// Basic usage would look like the below: +/// ```no_run +/// use neo_trinkey::hal::clock::GenericClockController; +/// use neo_trinkey::pac::Peripherals; +/// +/// let mut peripherals = Peripherals::take().unwrap(); +/// let mut clocks = GenericClockController::with_internal_32kosc( +/// peripherals.GCLK, +/// &mut peripherals.PM, +/// &mut peripherals.SYSCTRL, +/// &mut peripherals.NVMCTRL, +/// ); +/// let pins = bsp::Pins::new(peripherals.PORT); +/// +/// let bus_allocator = bsp::usb_allocator( +/// peripherals.USB, +/// &mut clocks, +/// &mut peripherals.PM, +/// pins.usb_dm, +/// pins.usb_dp, +/// ); +/// ``` +/// However to take advantage of USB interrupts you will need, to do some unsafe +/// rust. See the USB code examples in the `examples/` directory of the project. +#[cfg(feature = "usb")] +pub fn usb_allocator( + usb: pac::USB, + clocks: &mut hal::clock::GenericClockController, + pm: &mut pac::PM, + dm: impl Into, + dp: impl Into, +) -> UsbBusAllocator { + let gclk0 = clocks.gclk0(); + let clock = &clocks.usb(&gclk0).unwrap(); + let (dm, dp) = (dm.into(), dp.into()); + UsbBusAllocator::new(UsbBus::new(clock, pm, dm, dp, usb)) +} diff --git a/crates.json b/crates.json index b54a02cfc02d..9efef8326ce4 100644 --- a/crates.json +++ b/crates.json @@ -60,6 +60,10 @@ "tier": 1, "build": "cargo build --examples --features=unproven,usb" }, + "neo_trinkey": { + "tier": 2, + "build": "cargo build --examples --features=leds,unproven,usb" + }, "p1am_100": { "tier": 2, "build": "cargo build --examples --features=unproven,usb"