Working serial connection with handshake & props
This commit is contained in:
commit
81f76202f7
40
.cargo/config
Normal file
40
.cargo/config
Normal file
|
@ -0,0 +1,40 @@
|
|||
[target.thumbv7m-none-eabi]
|
||||
# uncomment this to make `cargo run` execute programs on QEMU
|
||||
runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel"
|
||||
|
||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||
# uncomment ONE of these three option to make `cargo run` start a GDB session
|
||||
# which option to pick depends on your system
|
||||
# runner = "arm-none-eabi-gdb -q -x openocd.gdb"
|
||||
runner = "gdb-multiarch -q -x openocd.gdb"
|
||||
# runner = "gdb -q -x openocd.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",
|
||||
|
||||
# LLD (shipped with the Rust toolchain) is used as the default linker
|
||||
"-C", "link-arg=-Tlink.x",
|
||||
|
||||
# if you run into problems with LLD switch to the GNU linker by commenting out
|
||||
# this line
|
||||
# "-C", "linker=arm-none-eabi-ld",
|
||||
|
||||
# if you need to link to pre-compiled C libraries provided by a C toolchain
|
||||
# use GCC as the linker by commenting out both lines above and then
|
||||
# uncommenting the three lines below
|
||||
# "-C", "linker=arm-none-eabi-gcc",
|
||||
# "-C", "link-arg=-Wl,-Tlink.x",
|
||||
# "-C", "link-arg=-nostartfiles",
|
||||
]
|
||||
|
||||
[build]
|
||||
# Pick ONE of these compilation targets
|
||||
target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
|
||||
# target = "thumbv7m-none-eabi" # Cortex-M3
|
||||
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
|
||||
# target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
|
||||
# target = "thumbv8m.base-none-eabi" # Cortex-M23
|
||||
# target = "thumbv8m.main-none-eabi" # Cortex-M33 (no FPU)
|
||||
# target = "thumbv8m.main-none-eabihf" # Cortex-M33 (with FPU)
|
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
**/*.rs.bk
|
||||
.#*
|
||||
.gdb_history
|
||||
Cargo.lock
|
||||
target/
|
||||
|
||||
# editor files
|
||||
.vscode/*
|
||||
!.vscode/*.md
|
||||
!.vscode/*.svd
|
||||
!.vscode/launch.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/extensions.json
|
28
Cargo.toml
Normal file
28
Cargo.toml
Normal file
|
@ -0,0 +1,28 @@
|
|||
[package]
|
||||
authors = ["asonix <asonix@asonix.dog>"]
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
name = "trinket-streamdeck"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
cortex-m = "0.6.0"
|
||||
cortex-m-rt = "0.6.10"
|
||||
cortex-m-semihosting = "0.3.3"
|
||||
once_cell = { version = "1.7.2", default-features = false }
|
||||
panic-halt = "0.2.0"
|
||||
smart-leds = "0.3"
|
||||
trinket_m0 = { version = "0.9.0", features = ["unproven", "usb"] }
|
||||
usb-device = "0.2"
|
||||
usbd-serial = "0.1"
|
||||
|
||||
# this lets you use `cargo fix`!
|
||||
[[bin]]
|
||||
name = "trinket-streamdeck"
|
||||
test = false
|
||||
bench = false
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1 # better optimizations
|
||||
debug = true # symbols are nice and they don't increase the size on Flash
|
||||
lto = true # better optimizations
|
31
build.rs
Normal file
31
build.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
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());
|
||||
|
||||
// By default, Cargo will re-run a build script whenever
|
||||
// any file in the project changes. By specifying `memory.x`
|
||||
// here, we ensure the build script is only re-run when
|
||||
// `memory.x` is changed.
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
}
|
36
deploy.sh
Executable file
36
deploy.sh
Executable file
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
PROJECT=${1:-trinket-streamdeck}
|
||||
|
||||
function bossac() {
|
||||
$HOME/snap/arduino/current/.arduino15/packages/arduino/tools/bossac/1.8.0-48-gb176eee/bossac "${@:1}"
|
||||
}
|
||||
|
||||
set -e
|
||||
|
||||
cargo build --release
|
||||
arm-none-eabi-objcopy -O binary \
|
||||
target/thumbv6m-none-eabi/release/$PROJECT \
|
||||
target/thumbv6m-none-eabi/release/$PROJECT.bin
|
||||
|
||||
ports="$(ls /dev/ | grep 'ttyACM')"
|
||||
|
||||
for port in $ports; do
|
||||
if [ "$port" == "" ]; then
|
||||
echo "Port missing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
device="/dev/$port"
|
||||
|
||||
printf "connecting to $device"
|
||||
while ! $(/bin/stty -F $device ospeed 1200 2> /dev/null); do
|
||||
printf "."
|
||||
sleep 1
|
||||
done
|
||||
echo
|
||||
|
||||
bossac -i -d \
|
||||
--port "$port" -U -e -w -v -o 0x2000 \
|
||||
target/thumbv6m-none-eabi/release/$PROJECT.bin -R
|
||||
done
|
34
memory.x
Normal file
34
memory.x
Normal file
|
@ -0,0 +1,34 @@
|
|||
MEMORY
|
||||
{
|
||||
/* NOTE 1 K = 1 KiBi = 1024 bytes */
|
||||
/* TODO Adjust these memory regions to match your device memory layout */
|
||||
/* These values correspond to the LM3S6965, one of the few devices QEMU can emulate */
|
||||
FLASH : ORIGIN = 0x00000000 + 8K, LENGTH = 256K - 8K
|
||||
RAM : ORIGIN = 0x20000000, LENGTH = 32K
|
||||
}
|
||||
|
||||
/* This is where the call stack will be allocated. */
|
||||
/* The stack is of the full descending type. */
|
||||
/* You may want to use this variable to locate the call stack and static
|
||||
variables in different memory regions. Below is shown the default value */
|
||||
/* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */
|
||||
|
||||
/* You can use this symbol to customize the location of the .text section */
|
||||
/* If omitted the .text section will be placed right after the .vector_table
|
||||
section */
|
||||
/* This is required only on microcontrollers that store some configuration right
|
||||
after the vector table */
|
||||
/* _stext = ORIGIN(FLASH) + 0x400; */
|
||||
|
||||
/* Example of putting non-initialized variables into custom RAM locations. */
|
||||
/* This assumes you have defined a region RAM2 above, and in the Rust
|
||||
sources added the attribute `#[link_section = ".ram2bss"]` to the data
|
||||
you want to place there. */
|
||||
/* Note that the section will not be zero-initialized by the runtime! */
|
||||
/* SECTIONS {
|
||||
.ram2bss (NOLOAD) : ALIGN(4) {
|
||||
*(.ram2bss);
|
||||
. = ALIGN(4);
|
||||
} > RAM2
|
||||
} INSERT AFTER .bss;
|
||||
*/
|
79
src/handshake.rs
Normal file
79
src/handshake.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
use crate::hal::usb::UsbBus;
|
||||
use cortex_m::peripheral::SYST;
|
||||
use usbd_serial::SerialPort;
|
||||
|
||||
pub(crate) struct Handshake {
|
||||
step_1_complete: bool,
|
||||
step_2_complete: bool,
|
||||
buffer: [u8; 16],
|
||||
timestamp: u32,
|
||||
}
|
||||
|
||||
impl Handshake {
|
||||
pub(crate) const fn new() -> Self {
|
||||
Handshake {
|
||||
step_1_complete: false,
|
||||
step_2_complete: false,
|
||||
buffer: [0; 16],
|
||||
timestamp: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_complete(&self) -> bool {
|
||||
self.step_1_complete && self.step_2_complete
|
||||
}
|
||||
|
||||
pub(crate) fn perform(
|
||||
&mut self,
|
||||
input: &[u8; 32],
|
||||
serial: &mut SerialPort<UsbBus>,
|
||||
) -> Option<bool> {
|
||||
if self.is_complete() {
|
||||
self.step_1_complete = false;
|
||||
self.step_2_complete = false;
|
||||
}
|
||||
|
||||
if !self.step_1_complete {
|
||||
self.timestamp = SYST::get_current();
|
||||
self.step_1(input, serial)?;
|
||||
self.step_1_complete = true;
|
||||
return Some(true);
|
||||
} else if !self.step_2_complete {
|
||||
self.step_2_complete = self.step_2(input);
|
||||
if !self.step_2_complete {
|
||||
self.step_1_complete = false;
|
||||
}
|
||||
return Some(true);
|
||||
}
|
||||
|
||||
if self.step_1_complete
|
||||
&& !self.step_2_complete
|
||||
&& self.timestamp > SYST::get_current() + 100
|
||||
{
|
||||
self.step_1_complete = false;
|
||||
}
|
||||
|
||||
Some(false)
|
||||
}
|
||||
|
||||
fn step_1(&mut self, input: &[u8; 32], serial: &mut SerialPort<UsbBus>) -> Option<()> {
|
||||
for (i, (c1, c2)) in input[..16].iter().zip(&input[16..]).enumerate() {
|
||||
self.buffer[i] = c1 ^ c2;
|
||||
}
|
||||
|
||||
serial.write(&self.buffer).ok()?;
|
||||
serial.flush().ok()?;
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn step_2(&self, input: &[u8; 32]) -> bool {
|
||||
let mut succeeded = true;
|
||||
|
||||
for ((c1, c2), c3) in input[..16].iter().zip(&input[16..]).zip(&self.buffer) {
|
||||
succeeded = succeeded || *c1 == *c2 && *c1 == *c3;
|
||||
}
|
||||
|
||||
succeeded
|
||||
}
|
||||
}
|
207
src/main.rs
Normal file
207
src/main.rs
Normal file
|
@ -0,0 +1,207 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use panic_halt as _; // you can put a breakpoint on `rust_begin_unwind` to catch panics
|
||||
use trinket_m0 as hal;
|
||||
|
||||
use core::cell::RefCell;
|
||||
use cortex_m::{asm::delay as cycle_delay, interrupt::Mutex, peripheral::NVIC};
|
||||
use hal::{
|
||||
clock::GenericClockController,
|
||||
entry,
|
||||
pac::{interrupt, CorePeripherals, Peripherals},
|
||||
prelude::_atsamd_hal_embedded_hal_digital_v2_OutputPin,
|
||||
timer::SpinTimer,
|
||||
usb::UsbBus,
|
||||
Dotstar,
|
||||
};
|
||||
use once_cell::unsync::OnceCell;
|
||||
use smart_leds::{SmartLedsWrite, RGB8};
|
||||
use usb_device::{bus::UsbBusAllocator, prelude::*};
|
||||
use usbd_serial::{SerialPort, USB_CLASS_CDC};
|
||||
|
||||
mod handshake;
|
||||
mod unsafe_sync;
|
||||
|
||||
use handshake::Handshake;
|
||||
use unsafe_sync::UnsafeSync;
|
||||
|
||||
static NAME: &'static [u8] = b"Streamdeck";
|
||||
static AUTHOR: &'static [u8] = b"asonix";
|
||||
|
||||
static USB_ALLOCATOR: UnsafeSync<OnceCell<UsbBusAllocator<UsbBus>>> =
|
||||
UnsafeSync::new(OnceCell::new());
|
||||
|
||||
static USB_BUS: Mutex<RefCell<Option<UsbDevice<UsbBus>>>> = Mutex::new(RefCell::new(None));
|
||||
static USB_SERIAL: Mutex<RefCell<Option<SerialPort<UsbBus>>>> = Mutex::new(RefCell::new(None));
|
||||
static HANDSHAKE: Mutex<RefCell<Handshake>> = Mutex::new(RefCell::new(Handshake::new()));
|
||||
|
||||
// section 9.3.3 of datasheet for SAMD21
|
||||
fn serial_number(id: &mut [u8; 16]) {
|
||||
let generator: [*const [u8; 4]; 4] = [
|
||||
0x0080A00C as *const [u8; 4],
|
||||
0x0080A040 as *const [u8; 4],
|
||||
0x0080A044 as *const [u8; 4],
|
||||
0x0080A048 as *const [u8; 4],
|
||||
];
|
||||
|
||||
for i in 0..4 {
|
||||
id[i * 4 + 0] = unsafe { (*generator[i])[3] };
|
||||
id[i * 4 + 1] = unsafe { (*generator[i])[2] };
|
||||
id[i * 4 + 2] = unsafe { (*generator[i])[1] };
|
||||
id[i * 4 + 3] = unsafe { (*generator[i])[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 mut pins = hal::Pins::new(peripherals.PORT);
|
||||
let mut red_led = pins.d13.into_open_drain_output(&mut pins.port);
|
||||
|
||||
let usb_allocator = hal::usb_allocator(
|
||||
peripherals.USB,
|
||||
&mut clocks,
|
||||
&mut peripherals.PM,
|
||||
pins.usb_dm,
|
||||
pins.usb_dp,
|
||||
&mut pins.port,
|
||||
);
|
||||
|
||||
// SAFETY: No interrupt will access USB_ALLOCATOR before this point, since it is only
|
||||
// referenced from inside the USB_SERIAL mutex and the USB_BUS mutex, which have not been set
|
||||
// yet.
|
||||
//
|
||||
// SAFETY: This is the only line of the program that modifies USB_ALLOCATOR
|
||||
let allocator = unsafe {
|
||||
USB_ALLOCATOR.get().set(usb_allocator).ok().unwrap();
|
||||
USB_ALLOCATOR.get().get().unwrap()
|
||||
};
|
||||
|
||||
cortex_m::interrupt::free(|cs| {
|
||||
let serial = USB_SERIAL.borrow(cs);
|
||||
let mut serial = serial.borrow_mut();
|
||||
*serial = Some(SerialPort::new(allocator));
|
||||
});
|
||||
|
||||
cortex_m::interrupt::free(|cs| {
|
||||
let bus = USB_BUS.borrow(cs);
|
||||
let mut bus = bus.borrow_mut();
|
||||
*bus = Some(
|
||||
UsbDeviceBuilder::new(allocator, UsbVidPid(0x16c0, 0x27dd))
|
||||
.manufacturer("Aode Lion")
|
||||
.product("Streamdeck")
|
||||
.serial_number("AAAA")
|
||||
.device_class(USB_CLASS_CDC)
|
||||
.build(),
|
||||
);
|
||||
});
|
||||
|
||||
unsafe {
|
||||
core.NVIC.set_priority(interrupt::USB, 1);
|
||||
NVIC::unmask(interrupt::USB);
|
||||
}
|
||||
|
||||
let mut rgb = Dotstar {
|
||||
ci: pins.dotstar_ci,
|
||||
di: pins.dotstar_di,
|
||||
nc: pins.dotstar_nc,
|
||||
}
|
||||
.init(SpinTimer::new(12), &mut pins.port);
|
||||
let off = RGB8 { r: 0, g: 0, b: 0 };
|
||||
rgb.write([off].iter().cloned()).unwrap();
|
||||
red_led.set_low().unwrap();
|
||||
|
||||
loop {
|
||||
cycle_delay(15 * 1024 * 1024);
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_usb() -> Option<()> {
|
||||
let ready = cortex_m::interrupt::free(|cs1| {
|
||||
cortex_m::interrupt::free(|cs2| {
|
||||
let mut usb_dev = USB_BUS.borrow(cs1).borrow_mut();
|
||||
let usb_dev = usb_dev.as_mut()?;
|
||||
let mut serial = USB_SERIAL.borrow(cs2).borrow_mut();
|
||||
let serial = serial.as_mut()?;
|
||||
|
||||
Some(usb_dev.poll(&mut [&mut *serial]))
|
||||
})
|
||||
})?;
|
||||
|
||||
if !ready {
|
||||
return Some(());
|
||||
}
|
||||
|
||||
let mut buf = [0u8; 32];
|
||||
|
||||
let count = cortex_m::interrupt::free(|cs| {
|
||||
USB_SERIAL
|
||||
.borrow(cs)
|
||||
.borrow_mut()
|
||||
.as_mut()?
|
||||
.read(&mut buf)
|
||||
.ok()
|
||||
})?;
|
||||
|
||||
if count == 32 {
|
||||
let res = cortex_m::interrupt::free(|cs| {
|
||||
cortex_m::interrupt::free(|cs2| {
|
||||
let mut serial = USB_SERIAL.borrow(cs2).borrow_mut();
|
||||
let serial = serial.as_mut()?;
|
||||
HANDSHAKE.borrow(cs).borrow_mut().perform(&buf, serial)
|
||||
})
|
||||
});
|
||||
if res? {
|
||||
return Some(());
|
||||
}
|
||||
}
|
||||
|
||||
let is_complete = cortex_m::interrupt::free(|cs| HANDSHAKE.borrow(cs).borrow().is_complete());
|
||||
if !is_complete {
|
||||
return None;
|
||||
}
|
||||
|
||||
if count == 5 && buf.starts_with(b"ident") {
|
||||
let mut sn = [0u8; 16];
|
||||
serial_number(&mut sn);
|
||||
write_length_delim(&sn)?;
|
||||
} else if count == 4 && buf.starts_with(b"name") {
|
||||
write_length_delim(NAME)?;
|
||||
} else if count == 6 && buf.starts_with(b"author") {
|
||||
write_length_delim(AUTHOR)?;
|
||||
} else {
|
||||
write_length_delim(&buf[..count])?;
|
||||
}
|
||||
|
||||
cortex_m::interrupt::free(|cs| {
|
||||
let mut serial = USB_SERIAL.borrow(cs).borrow_mut();
|
||||
let serial = serial.as_mut()?;
|
||||
serial.flush().ok()?;
|
||||
Some(())
|
||||
})?;
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn write_length_delim(bytes: &[u8]) -> Option<()> {
|
||||
cortex_m::interrupt::free(|cs| {
|
||||
let mut serial = USB_SERIAL.borrow(cs).borrow_mut();
|
||||
let serial = serial.as_mut()?;
|
||||
serial.write(&[bytes.len() as u8]).ok()?;
|
||||
serial.write(bytes).ok()?;
|
||||
Some(())
|
||||
})
|
||||
}
|
||||
|
||||
#[interrupt]
|
||||
fn USB() {
|
||||
poll_usb();
|
||||
}
|
13
src/unsafe_sync.rs
Normal file
13
src/unsafe_sync.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
pub(crate) struct UnsafeSync<T>(T);
|
||||
|
||||
impl<T> UnsafeSync<T> {
|
||||
pub(crate) const fn new(item: T) -> Self {
|
||||
UnsafeSync(item)
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn get(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Sync for UnsafeSync<T> where T: Send {}
|
Loading…
Reference in a new issue