diff --git a/boards/feather_m0/.cargo/config.toml b/boards/feather_m0/.cargo/config.toml index e209d0667d77..3bdfbd03d1ce 100644 --- a/boards/feather_m0/.cargo/config.toml +++ b/boards/feather_m0/.cargo/config.toml @@ -5,9 +5,12 @@ target = "thumbv6m-none-eabi" 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", - "-C", "link-arg=-Tdefmt.x" # uncomment if using defmt + "-C", + "link-arg=--nmagic", + "-C", + "link-arg=-Tlink.x", + "-C", + "link-arg=-Tdefmt.x", # uncomment if using defmt ] [target.thumbv6m-none-eabi] diff --git a/boards/trrs_trinkey/.cargo/config.toml b/boards/trrs_trinkey/.cargo/config.toml new file mode 100644 index 000000000000..e209d0667d77 --- /dev/null +++ b/boards/trrs_trinkey/.cargo/config.toml @@ -0,0 +1,15 @@ +# samd21 is a Cortex-M0 and thus thumbv6m + +[build] +target = "thumbv6m-none-eabi" +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", + "-C", "link-arg=-Tdefmt.x" # uncomment if using defmt +] + +[target.thumbv6m-none-eabi] +runner = 'arm-none-eabi-gdb' +#runner = 'probe-run --chip ATSAMD21G18A' diff --git a/boards/trrs_trinkey/.gdbinit b/boards/trrs_trinkey/.gdbinit new file mode 100644 index 000000000000..91c55a0f03e1 --- /dev/null +++ b/boards/trrs_trinkey/.gdbinit @@ -0,0 +1,2 @@ +target remote :3333 +file target/thumbv6m-none-eabi/debug/examples/blinky_rtic diff --git a/boards/trrs_trinkey/CHANGELOG.md b/boards/trrs_trinkey/CHANGELOG.md new file mode 100644 index 000000000000..1d013ff92fa8 --- /dev/null +++ b/boards/trrs_trinkey/CHANGELOG.md @@ -0,0 +1,6 @@ +# 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). diff --git a/boards/trrs_trinkey/Cargo.toml b/boards/trrs_trinkey/Cargo.toml new file mode 100644 index 000000000000..e8626a5be494 --- /dev/null +++ b/boards/trrs_trinkey/Cargo.toml @@ -0,0 +1,112 @@ +[package] +authors = ["Erik Fong "] +categories = ["embedded", "hardware-support", "no-std"] +description = "Board Support crate for the Adafruit TRRS Trinkey" +edition = "2021" +keywords = ["no-std", "arm", "cortex-m", "embedded-hal"] +license = "MIT OR Apache-2.0" +name = "trrs_trinkey" +readme = "README.md" +repository = "https://github.com/atsamd-rs/atsamd" +resolver = "2" +version = "0.20.1" + +# for cargo flash +[package.metadata] +chip = "ATSAMD21G18A" + +[dependencies.cortex-m-rt] +optional = true +version = "0.7" + +[dependencies.atsamd-hal] +default-features = false +path = "../../hal" +version = "0.23.1" + +[dependencies.cortex-m] +features = ["critical-section-single-core"] +version = "0.7" + +[dependencies.rtic] +features = ["thumbv6-backend"] +optional = true +version = "2.1.1" + +[dependencies.usb-device] +optional = true +version = "0.3.1" + +[dependencies.embedded-sdmmc] +optional = true +version = "0.3" + +[dependencies] +smart-leds = { version = "0.3.0", optional = true } +ws2812-timer-delay = { version = "0.3.0", features = ["slow"], optional = true } + +[dev-dependencies] +cortex-m = "0.7" +cortex-m-semihosting = "0.3" +defmt = "0.3" +defmt-rtt = "0.4" +drogue-nom-utils = "0.1" +embassy-executor = { version = "0.6.2", features = [ + "arch-cortex-m", + "executor-thread", + "task-arena-size-64", +] } +embedded-graphics = "0.7.1" +heapless = "0.8" +nom = { version = "5", default-features = false } +panic-halt = "0.2" +panic-probe = "0.3" +panic-semihosting = "0.6" +rtic-monotonics = { version = "1.3.0", features = [ + "cortex-m-systick", + "systick-10khz", +] } +ssd1306 = "0.7" +usbd-hid = "0.9.0" +usbd-serial = "0.2" + +[features] +# ask the HAL to enable atsamd21g support +default = ["rt", "atsamd-hal/samd21g"] +leds = ["ws2812-timer-delay", "smart-leds"] +rt = ["cortex-m-rt", "atsamd-hal/samd21g-rt"] +usb = ["atsamd-hal/usb", "usb-device"] +use_rtt = ["atsamd-hal/use_rtt"] +dma = ["atsamd-hal/dma"] +max-channels = ["dma", "atsamd-hal/max-channels"] +# Enable async support from atsamd-hal +async = ["atsamd-hal/async"] +rtic = ["dep:rtic", "atsamd-hal/rtic"] +sdmmc = ["embedded-sdmmc", "atsamd-hal/sdmmc"] +use_semihosting = [] + +[[example]] +name = "blinky_basic" +required-features = ["leds"] + +[[example]] +name = "blinky_embassy" +required-features = ["rtic", "leds"] + +[[example]] +name = "i2c" +required-features = ["dma", "usb"] + +[[example]] +name = "pwm" + +[[example]] +name = "uart" + +[[example]] +name = "usb_echo" +required-features = ["usb"] + +[[example]] +name = "usb_hid" +required-features = ["usb"] diff --git a/boards/trrs_trinkey/README.md b/boards/trrs_trinkey/README.md new file mode 100644 index 000000000000..7ac9b8777132 --- /dev/null +++ b/boards/trrs_trinkey/README.md @@ -0,0 +1,38 @@ +# Adafruit TRRS Trinkey Board Support Crate + +This crate provides a type-safe API for working with the [Adafruit TRRS Trinkey +board](https://www.adafruit.com/product/5954) ([Adafruit tutorial page](https://learn.adafruit.com/adafruit-trrs-trinkey/)). + +
+ Adafruit TRRS Trinkey Pinout +
Adafruit TRRS Trinkey Pinout
+
+ +## 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 + +NOTE: `cargo-hf2` does not seem to work. It will require the following PR: https://github.com/jacobrosenthal/hf2-rs/pull/49 + +Check out the repository for examples: + +https://github.com/atsamd-rs/atsamd/tree/master/boards/trrs_trinkey/examples + +- Be in this directory `cd boards/trrs_trinkey` +- Put your device in bootloader mode usually by hitting the reset button twice. +- Build and upload in one step + +``` +$ cargo hf2 --features leds --release --example blinky_basic + Finished `release` profile [optimized + debuginfo] target(s) in 0.06s + Searching for a connected device with known vid/pid pair. + Trying Ok(Some("Adafruit Industries")) Ok(Some("TRRS Trinkey M0")) + Flashing "/home/quantum_p/LocalDocs/atsamd/target/thumbv6m-none-eabi/release/examples/blinky_basic" + Finished in 0.123s +$ +``` + +The `usb_hid` example is the most useful given what the intended use of the device is. If you need details about other peripherials, the examples from other boards like the `feather_m0` should be sufficient, but have not yet been explicitly tested on this board. diff --git a/boards/trrs_trinkey/build.rs b/boards/trrs_trinkey/build.rs new file mode 100644 index 000000000000..4bed4688f2c0 --- /dev/null +++ b/boards/trrs_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/trrs_trinkey/examples/blinky_basic.rs b/boards/trrs_trinkey/examples/blinky_basic.rs new file mode 100644 index 000000000000..4654a1c9dc34 --- /dev/null +++ b/boards/trrs_trinkey/examples/blinky_basic.rs @@ -0,0 +1,54 @@ +#![no_std] +#![no_main] + +use panic_halt as _; + +use trrs_trinkey as bsp; + +use bsp::entry; +use bsp::hal; + +use hal::clock::GenericClockController; +use hal::delay::Delay; +use hal::ehal::delay::DelayNs; +use hal::pac::{CorePeripherals, Peripherals}; +use hal::time::Hertz; +use hal::timer::TimerCounter; +use hal::timer_traits::InterruptDrivenTimer; + +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(Hertz::MHz(3).into_duration()); + 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 = 1; + let off = [RGB8::default(); NUM_LEDS]; + let on = [RGB8::new(0, 0, 5)]; + + loop { + ws2812.write(off.iter().cloned()).unwrap(); + delay.delay_ms(500); + ws2812.write(on.iter().cloned()).unwrap(); + delay.delay_ms(500); + } +} diff --git a/boards/trrs_trinkey/examples/blinky_embassy.rs b/boards/trrs_trinkey/examples/blinky_embassy.rs new file mode 100644 index 000000000000..b0af00c0380c --- /dev/null +++ b/boards/trrs_trinkey/examples/blinky_embassy.rs @@ -0,0 +1,55 @@ +//! Uses RTIC with systick to blink a led in an asynchronous fashion. +#![no_std] +#![no_main] + +use feather_m0 as bsp; + +#[cfg(not(feature = "use_semihosting"))] +use panic_halt as _; +#[cfg(feature = "use_semihosting")] +use panic_semihosting as _; + +use bsp::{hal, pac, pin_alias}; +use hal::clock::{ClockGenId, ClockSource, GenericClockController}; +use hal::prelude::*; +use hal::rtc::rtic::rtc_clock; + +// We can use the RTIC monotonic with embassy +hal::rtc_monotonic!(Mono, rtc_clock::ClockCustom<8_192>); + +// However, to do so, we need to define this, which is normally defined within +// RTIC. This sets the RTC monotonic interrupt priority to be the most +// important. +#[no_mangle] +static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8 = 1; + +#[embassy_executor::main] +async fn main(_s: embassy_executor::Spawner) { + let mut peripherals = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + let pins = bsp::Pins::new(peripherals.port); + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.gclk, + &mut peripherals.pm, + &mut peripherals.sysctrl, + &mut peripherals.nvmctrl, + ); + + // Set the RTC clock to use a 8.192 kHz clock derived from the external 32 kHz + // oscillator. + let rtc_clock_src = clocks + .configure_gclk_divider_and_source(ClockGenId::Gclk2, 4, ClockSource::Xosc32k, false) + .unwrap(); + clocks.configure_standby(ClockGenId::Gclk2, true); + let _ = clocks.rtc(&rtc_clock_src).unwrap(); + + let mut red_led: bsp::RedLed = pin_alias!(pins.red_led).into(); + + // Start the monotonic + Mono::start(peripherals.rtc); + + loop { + let _ = red_led.toggle(); + Mono::delay(1u64.secs()).await; + } +} diff --git a/boards/trrs_trinkey/examples/i2c.rs b/boards/trrs_trinkey/examples/i2c.rs new file mode 100644 index 000000000000..c318158c49b3 --- /dev/null +++ b/boards/trrs_trinkey/examples/i2c.rs @@ -0,0 +1,147 @@ +/// This code will periodically read 2 bytes from an I2C device and store them. +/// When something is written to the serial interface (cannot just send a blank message, send a newline or smth), +/// It will respond with whatever was read form the device +/// in the case of the example, read the raw temperature value from the TMP117 and print it over serial +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU16, AtomicU8}; + +#[cfg(not(feature = "use_semihosting"))] +use panic_halt as _; +#[cfg(feature = "use_semihosting")] +use panic_semihosting 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::embedded_io::Write; +use usbd_serial::{SerialPort, USB_CLASS_CDC}; + +use bsp::hal; +use bsp::pac; +use bsp::{entry, pin_alias}; +use hal::clock::GenericClockController; +use hal::dmac::{DmaController, PriorityLevel}; +use hal::ehal::i2c::I2c; +use hal::fugit::RateExtU32; +use hal::prelude::*; +use hal::usb::UsbBus; +use pac::{interrupt, CorePeripherals, Peripherals}; +use trrs_trinkey as bsp; +use trrs_trinkey::i2c_master; + +static TEMP: AtomicU16 = AtomicU16::new(0); + +#[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); + + // USB setup so that data can be logged to the host machine over serial + // This code was taken from the usb_echo example. + // This example was copied from an example for another board.s + { + 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)) + .strings(&[StringDescriptors::new(LangID::EN) + .manufacturer("Fake company") + .product("Serial port") + .serial_number("TEST")]) + .expect("Failed to set strings") + .device_class(USB_CLASS_CDC) + .build(), + ); + } + + unsafe { + core.NVIC.set_priority(interrupt::USB, 1); + NVIC::unmask(interrupt::USB); + } + } + + // I2C setup items + let mut pm = peripherals.pm; + let dmac = peripherals.dmac; + + // Take SDA and SCL + let (sda, scl) = (pins.sda, pins.scl); + + // Setup DMA channels for later use (Optional) + let mut dmac = DmaController::init(dmac, &mut pm); + let channels = dmac.split(); + let chan0 = channels.0.init(PriorityLevel::Lvl0); + + // See the source for this function if more detailed configuration is desired. + let mut i2c = i2c_master( + &mut clocks, + 100.kHz(), + peripherals.sercom2, + &mut pm, + sda, + scl, + ) + .with_dma_channel(chan0); //Optional + + let mut received = [0x00; 2]; + + loop { + cycle_delay(10 * 1024 * 1024); + + // Address of the temperature sensor I have (TMP117) + const I2C_DEV_ADDR: u8 = 0x48; + i2c.read(I2C_DEV_ADDR, &mut received).unwrap(); + + let tmp = u16::from_be_bytes(received); + + TEMP.store(tmp, core::sync::atomic::Ordering::Relaxed); + } +} + +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() { + let current_temp = TEMP.load(core::sync::atomic::Ordering::Relaxed); + + 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) { + writeln!(serial, "Raw {current_temp}").ok(); + }; + }; + }; + }; +} + +#[interrupt] +fn USB() { + poll_usb(); +} diff --git a/boards/trrs_trinkey/examples/uart.rs b/boards/trrs_trinkey/examples/uart.rs new file mode 100644 index 000000000000..b7a28e3a3ea4 --- /dev/null +++ b/boards/trrs_trinkey/examples/uart.rs @@ -0,0 +1,91 @@ +//! This example shows how to use the UART to perform transfers using the +//! embedded-hal-nb traits. +//! DISCLAIMER - The UART pinout does not follow any specific standard +//! Further, the pinout of the MCU on the TRRS jack does not seem to work with any sort of "normal" serial trrs adapters I have seen + +#![no_std] +#![no_main] + +use atsamd_hal::ehal::digital::OutputPin; +use atsamd_hal::ehal::digital::StatefulOutputPin; +use atsamd_hal::fugit::Rate; +use atsamd_hal::gpio::AlternateD; +use atsamd_hal::gpio::Pin; +use atsamd_hal::gpio::PA04; +use atsamd_hal::gpio::PA06; +use atsamd_hal::sercom::uart; +use atsamd_hal::sercom::uart::BaudMode; +use atsamd_hal::sercom::uart::Oversampling; +#[cfg(not(feature = "use_semihosting"))] +use panic_halt as _; +#[cfg(feature = "use_semihosting")] +use panic_semihosting as _; + +use bsp::hal; +use bsp::pac; +use hal::nb; +use trrs_trinkey as bsp; + +use bsp::{entry, periph_alias}; +use hal::clock::GenericClockController; +use hal::ehal_nb::serial::{Read, Write}; + +use pac::Peripherals; + +#[entry] +fn main() -> ! { + 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 pm = peripherals.pm; + let pins = bsp::Pins::new(peripherals.port); + + // Take peripheral and pins + let uart_sercom = periph_alias!(peripherals.trrs_sercom); + // Referenced https://raw.githubusercontent.com/adafruit/Adafruit-TRRS-Trinkey-PCB/refs/heads/main/Adafruit%20TRRS%20Trinkey%20PrettyPins.svg + // and https://onlinedocs.microchip.com/oxy/GUID-22527069-B4D6-49B9-BACC-3AF1C52EB48C-en-US-21/GUID-DEBE7851-16DD-4D3B-8C60-F2627E3B3DEE.html + // to make an arbitrary call on the TX/RX assignments. + let uart_tx: Pin = pins.ring_1.into_alternate(); + let uart_rx: Pin = pins.ring_2.into_alternate(); + + // Need to set a pin as ground + let mut sleeve_gnd = pins.sleeve.into_push_pull_output(); + sleeve_gnd.set_low(); + + let uart = { + let gclk0 = clocks.gclk0(); + let clock = &clocks.sercom0_core(&gclk0).unwrap(); + let baud = Rate::::Hz(9600); + let pads = uart::Pads::default().rx(uart_rx).tx(uart_tx); + uart::Config::new(&pm, uart_sercom, pads, clock.freq()) + .baud(baud, BaudMode::Fractional(Oversampling::Bits16)) + .enable() + }; + + // Split uart in rx + tx halves + let (mut rx, mut tx) = uart.split(); + + // Make buffers to store data to send/receive + let mut rx_buffer = [0x00; 50]; + let tx_buffer = b"Hello, world!"; + + loop { + // Send data. We block on each byte, but we could also perform some tasks while + // waiting for the byte to finish sending. + for c in tx_buffer.iter() { + nb::block!(tx.write(*c)).unwrap(); + } + + // Receive data. We block on each byte, but we could also perform some tasks + // while waiting for the byte to finish sending. + rx.flush_rx_buffer(); + for c in rx_buffer.iter_mut() { + *c = nb::block!(rx.read()).unwrap(); + } + } +} diff --git a/boards/trrs_trinkey/examples/usb_echo.rs b/boards/trrs_trinkey/examples/usb_echo.rs new file mode 100644 index 000000000000..bfa9a1094d93 --- /dev/null +++ b/boards/trrs_trinkey/examples/usb_echo.rs @@ -0,0 +1,115 @@ +#![no_std] +#![no_main] + +#[cfg(not(feature = "use_semihosting"))] +use panic_halt as _; +#[cfg(feature = "use_semihosting")] +use panic_semihosting 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 bsp::hal; +use bsp::pac; +use trrs_trinkey as bsp; + +use bsp::{entry, pin_alias}; +use hal::clock::GenericClockController; +use hal::prelude::*; +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); + + // Setup so an LED could be driven by being connected between the Tip and Sleeve pins. + // WARNING: from what I have found, the max current capability is 10mA, so it is suggested to keep the current below 7mA + // I do not have any guidance on how much current the IO pins can SINK. + // Source: https://forums.adafruit.com/viewtopic.php?t=123728, https://onlinedocs.microchip.com/oxy/GUID-22527069-B4D6-49B9-BACC-3AF1C52EB48C-en-US-20/GUID-AD9164C2-015D-4DEA-9A54-44165FBE92D0.html + let mut tip_pin = pins.tip.into_push_pull_output(); + tip_pin.set_drive_strength(true); + + // Configure the sleve pin to sink current (be ground) + let mut sleeve_pin = pins.sleeve.into_push_pull_output(); + sleeve_pin.set_drive_strength(true); + sleeve_pin.set_low(); + + 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)) + .strings(&[StringDescriptors::new(LangID::EN) + .manufacturer("Fake company") + .product("Serial port") + .serial_number("TEST")]) + .expect("Failed to set strings") + .device_class(USB_CLASS_CDC) + .build(), + ); + } + + unsafe { + core.NVIC.set_priority(interrupt::USB, 1); + NVIC::unmask(interrupt::USB); + } + + // Toggle Tip pin in a spin loop to demonstrate that USB is + // entirely interrupt driven. + loop { + cycle_delay(100 * 1024 * 1024); + tip_pin.toggle().ok(); + } +} + +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]).ok(); + } + }; + }; + }; + }; +} + +#[interrupt] +fn USB() { + poll_usb(); +} diff --git a/boards/trrs_trinkey/examples/usb_hid.rs b/boards/trrs_trinkey/examples/usb_hid.rs new file mode 100644 index 000000000000..1b40cf16b985 --- /dev/null +++ b/boards/trrs_trinkey/examples/usb_hid.rs @@ -0,0 +1,158 @@ +#![no_std] +#![no_main] +/// This example emulates a really bad Iambic Morse Key, +/// i.e. it is not usable because the timings are not useful presumably because of the polling rate and the ability to "interleave" the dots and dashes. +/// That being said, when the TIP and RING_1 pins of the TRRS jack are pulled low, +/// They give the keystrokes of '.' and '-' respectively. +/// This example was adapted from the code at https://github.com/atsamd-rs/atsamd/blob/ef02fe3e21031da27aaef629368105962363b370/boards/itsybitsy_m0/examples/twitching_usb_mouse.rs +use atsamd_hal::gpio::Input; +use atsamd_hal::gpio::Pin; +use atsamd_hal::gpio::PullUp; +use atsamd_hal::gpio::{PA02, PA06}; +use cortex_m::asm::wfi; +use cortex_m::interrupt::Mutex; +#[cfg(not(feature = "use_semihosting"))] +use panic_halt as _; +#[cfg(feature = "use_semihosting")] +use panic_semihosting as _; + +use cortex_m::peripheral::NVIC; +use usb_device::bus::UsbBusAllocator; +use usb_device::prelude::*; + +use bsp::hal; +use bsp::pac; +use trrs_trinkey as bsp; + +use bsp::entry; +use core::cell::OnceCell; +use cortex_m::interrupt::free as disable_interrupts; +use hal::clock::GenericClockController; +use hal::prelude::*; +use hal::usb::UsbBus; +use pac::{interrupt, CorePeripherals, Peripherals}; +use usbd_hid::descriptor::generator_prelude::*; +use usbd_hid::descriptor::KeyboardReport; +use usbd_hid::hid_class::HIDClass; + +static TR1PINS: Mutex>, Pin>)>> = + Mutex::new(OnceCell::new()); + +#[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); + + // Setup so an LED could be driven by being connected between the Tip and Sleeve pins. + // WARNING: from what I have found, the max current capability is 10mA, so it is suggested to keep the current below 7mA + // I do not have any guidance on how much current the IO pins can SINK. + // Source: https://forums.adafruit.com/viewtopic.php?t=123728, https://onlinedocs.microchip.com/oxy/GUID-22527069-B4D6-49B9-BACC-3AF1C52EB48C-en-US-20/GUID-AD9164C2-015D-4DEA-9A54-44165FBE92D0.html + let tip_pin = pins.tip.into_pull_up_input(); + let ring1_pin = pins.ring_1.into_pull_up_input(); + + // Disabling these pins to be certain. + let _tip_sw = pins.tip_switch.into_floating_disabled(); + let _ring1_sw = pins.ring_1_switch.into_floating_disabled(); + let _ring2 = pins.ring_2.into_floating_disabled(); + + disable_interrupts(|cs| { + let tr1_pins = TR1PINS.borrow(cs); + let _ = tr1_pins.get_or_init(|| (tip_pin, ring1_pin)); + }); + + // Configure the sleve pin to sink current (be ground) + let mut sleeve_pin = pins.sleeve.into_push_pull_output(); + sleeve_pin.set_drive_strength(true); + sleeve_pin.set_low(); + + 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_HID = Some(HIDClass::new(bus_allocator, KeyboardReport::desc(), 1)); + USB_BUS = Some( + UsbDeviceBuilder::new(bus_allocator, UsbVidPid(0x16c0, 0x27dd)) + .strings(&[StringDescriptors::new(LangID::EN) + .manufacturer("Fake company") + .product("Twitchy Mousey") + .serial_number("TEST")]) + .expect("Failed to set strings") + .build(), + ); + } + + unsafe { + core.NVIC.set_priority(interrupt::USB, 1); + NVIC::unmask(interrupt::USB); + } + + unsafe { + core.NVIC.set_priority(interrupt::USB, 1); + NVIC::unmask(interrupt::USB); + } + + loop { + wfi(); + } +} + +const DAH_KEYCODE: u8 = 0x2D; +const DIT_KEYCODE: u8 = 0x37; + +static mut USB_ALLOCATOR: Option> = None; +static mut USB_BUS: Option> = None; +static mut USB_HID: Option> = None; + +fn poll_usb() { + let mut tip_low = false; + let mut ring_low = false; + disable_interrupts(|cs| { + let pins = TR1PINS.borrow(cs).get().unwrap(); + tip_low = pins.0.is_low().unwrap(); + ring_low = pins.1.is_low().unwrap(); + }); + + let mut report = KeyboardReport { + modifier: 0, + reserved: 0, + leds: 0, + keycodes: [0x0, 0, 0, 0, 0, 0], + }; + + if tip_low { + report.keycodes[0] = DIT_KEYCODE; + } + + if ring_low { + report.keycodes[1] = DAH_KEYCODE; + } + + disable_interrupts(|_| unsafe { USB_HID.as_mut().map(|hid| hid.push_input(&report)) }); + + unsafe { + if let (Some(usb_dev), Some(hid)) = (USB_BUS.as_mut(), USB_HID.as_mut()) { + usb_dev.poll(&mut [hid]); + } + }; +} + +#[interrupt] +fn USB() { + poll_usb(); +} diff --git a/boards/trrs_trinkey/memory.x b/boards/trrs_trinkey/memory.x new file mode 100644 index 000000000000..86d2837aacfd --- /dev/null +++ b/boards/trrs_trinkey/memory.x @@ -0,0 +1,9 @@ +MEMORY +{ + /* Leave 8k for the default bootloader on the TRRS Trinkey */ + /* This information was specified in https://github.com/adafruit/uf2-samdx1?tab=readme-ov-file#configuration */ + FLASH (rx) : ORIGIN = 0x00000000 + 8K, LENGTH = 256K - 8K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 32K +} +_stack_start = ORIGIN(RAM) + LENGTH(RAM); + diff --git a/boards/trrs_trinkey/src/lib.rs b/boards/trrs_trinkey/src/lib.rs new file mode 100644 index 000000000000..5837658443de --- /dev/null +++ b/boards/trrs_trinkey/src/lib.rs @@ -0,0 +1,142 @@ +#![no_std] + +#[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; +use hal::time::Hertz; + +#[cfg(feature = "usb")] +use hal::usb::{usb_device::bus::UsbBusAllocator, UsbBus}; + +hal::bsp_peripherals!( + /// I2c Put on Sercom2 since we want to leave Sercom0 for user defined operations with the TRRS Jack + Sercom2 { I2cSercom } + Sercom0 {TrrsSercom} +); + +/// Definitions related to pins and pin aliases +pub mod pins { + use super::hal; + + hal::bsp_pins!( + PA02 { + /// TRRS Tip Pin + name: tip + } + PA03 { + /// TRRS Tip Switch Pin + name: tip_switch + aliases: { + PullUpInput: TipSwitchSense + } + } + PA06 { + /// TRRS First Ring Pin + name: ring_1 + } + PA07 { + /// TRRS First Ring Pin Switch + name: ring_1_switch + aliases: { + PullUpInput: Ring1SwitchSense + } + } + PA04 { + /// TRRS Second Ring Pin + name: ring_2 + } + PA05 { + /// TRRS Sleve Pin + name: sleeve + } + + PA08 { + /// The I2C data line + name: sda + aliases: { + AlternateD: Sda + } + } + PA09 { + /// The I2C clock line + name: scl + aliases: { + AlternateD: Scl + } + } + + PA24 { + /// The USB D- pad + name: usb_dm + aliases: { + AlternateG: UsbDm + } + } + PA25 { + /// The USB D+ pad + name: usb_dp + aliases: { + AlternateG: UsbDp + } + } + + PA01 { + /// Neopixel data + name: neo_pixel + } + + + ); +} +pub use pins::*; + +/// 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, + pm: &mut pac::Pm, + 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(pm, sercom, pads, freq).baud(baud).enable() +} + +#[cfg(feature = "usb")] +/// Convenience function for setting up USB +pub fn usb_allocator( + usb: pac::Usb, + clocks: &mut 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 c58ab16cbdd8..b972e2e9dc68 100644 --- a/crates.json +++ b/crates.json @@ -171,6 +171,11 @@ "tier": 2, "build": "cargo build --examples --all-features", "target": "thumbv6m-none-eabi" + }, + "trrs_trinkey": { + "tier": 2, + "build": "cargo build --examples --all-features", + "target": "thumbv6m-none-eabi" } }, "hal_doc_variants": {