Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Mockup a Tessel API covering the same basic APIs as Tessel JS
// Examples
mod examples {
fn main() {
// Different ways to get a single pwm pin.
// These earlier ones are a bit wasteful as they also create the leds
// and other port to start. Meaning both ports need to be created and
// then with proper scoping one should shutdown. We should be able to
// use scoping to automatically control if a port is open or closed. If
// no pins are in use for a port, it should automatically close.
// Each of these must be unwrapped. If there is already a Tessel, Port,
// or Pin instance for what we want the first part of these calls will
// be an error instead of the object. Each object can only be owned at
// one time. If multiple things need to access the same physical
// resource they should share a Mutex or other relevant object between
// them containing the resource. We shouldn't hide the fact that they
// are a limited resource by automatically providing a cloning
// interface. Either the user can do that or we can provide a more
// explicit even higher level object, like a TesselProxy, that owns the
// single resource.
let mut pwm = Tessel::new().expect("a Tessel object")[5].into_pwm().expect("a PWM pin");
let mut pwm = Tessel::new().expect("a Tessel object").ports.a.into_pwm().expect("a PWM pin");
let mut pwm = Tessel::ports().expect("a Tessel object")[5].into_pwm().expect("a PWM pin");
let mut pwm = Tessel::ports().expect("a Tessel object").a.into_pwm().expect("a PWM pin");
// These next two open a specific port to access the pin.
let mut pwm = Port::new("a").expect("a Port A object").pin[5].into_pwm().expect("a PWM pin");
let mut pwm = Port::new("a").expect("a Port A object").into_pwm().expect("a PWM ping");
// This last one opens the specific pin.
let mut pwm = Pin::new("a", 5).expect("the A5 Pin").into_pwm().expect("a PWM pin");
// A port must be available for global calls like pwm_frequency.
// Otherwise it will return an error.
Tessel::pwm_frequency(1000).expect("PWM frequency to be set");
let mut duty = 0.0;
let mut dir = 0.016;
loop {
pwm.duty_cycle(duty).expect("PWM duty cycle to be updated");
duty += dir;
if duty > 1 {
duty = 1;
dir = -0.016;
if duty < 0 {
duty = 0;
dir = 0.016;
mod relay_mono {
pub struct RelayArray {
items: [RelayItem; 2],
pub struct RelayItem {
pin: tessel::Pin,
state: Option<bool>,
impl RelayArray {
pub fn new(port: tessel::Port) -> tessel::Result<RelayArray> {
// let (_, _, _, _, _, pin1, pin2, _) = port.into_pins();
let pins =|pin| {
Ok(RelayItem {
pin: try!(pin.into_digital()),
state: false,
RelayArray {
items: [,,
pub fn connect(&mut self) -> tessel::Result<()> {
// Set Digitals as outputs.
for item in self.iter_mut() {
pub fn len(&self) -> usize {
pub fn iter(&self) -> slice::Iter<RelayItem> {
pub fn iter_mut(&mut self) -> slice::IterMut<RelayItem> {
impl Index<usize> for RelayArray {
type Output = RelayItem;
fn index(&self, index: usize) -> &Self::Output {
impl IndexMut<usize> for RelayArray {
type Output = RelayItem;
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
impl RelayItem {
pub fn get(&self) -> Result<usize> {
self.state.ok_or(Err(tessel::Error::new(tessel::ErrorKind::Uninitialized, "RelayArray item is not initialized.")))
pub fn set(&mut self, value: usize) -> tessel::Result<()> {
if value > 1 {
return Err(tessel::Error::new(tessel::ErrorKind::Range, "Cannot set RelayArray item to a value higher than 1."));
self.state = Some(value);
fn main() {
// A Ports object can be destructed so its a and b port can be owned by
// seperate objects or threads.
let Ports { a: mut a, b: mut b } = Tessel::ports().expect("a Ports object");
thread::spawn(move || {
loop {
// Reference instances can be an easy way to reuse a port or
// pin. Once the reference is dropped the port or pin can be
// used for another.
a.interrupt().expect("a Interrupt pin").wait_rise().expect("the Interrupt pin rose");
let mut i2c = a.i2c().expect("a I2c pin");
// Do i2c data transfer
thread::spawn(move || {
Tessel::pwm_frequency(1000).expect("that PWM frequency is set");
let mut pwm = b.into_pwm().expect("a PWM pin");
// Light up a LED
fn main() {
let port = Port::new("b").expect("a Port B object");
// Pull off the pins taking ownership of them.
let interrupt ="an Interrupt pin");
let digital ="a Digital pin");
// Multi pin communication protocols require us to have mutable
// references or ownership of all needed ports. I2C and UART need two
// pins or one port (consuming the port, so using I2C and UART at the
// same time or other pins means we need to destruct the port by taking
// Pins from it). Since we are removing pins here the "index" will be
// changing as the Port cannot retain a reference.
let uart_pins = (,;
// Different vector methods or order of removing can make this easier to
// understand.
let uart_pins =;
let uart = uart_pins.into_uart().expect("a Uart pin wrapper");
loop {
interrupt.wait_rise().expect("the Interrupt pin rose");
if"the Digital pin was read") {
uart.write(&[0x01, 0x02]).expect("Uart was written to");
else {
uart.write(&[0x03, 0x04]).expect("Uart was written to");
fn main() {
let Tessel { led: mut leds, .. } = Tessel::new().expect("a Tessel object");
let mut leds = Tessel::leds().expect("the Tessel Leds");
led[2].on().expect("led 2 was updated");
loop {
led[2].toggle().expect("led 2 was updated");
led[3].toggle().expect("led 3 was updated");
fn main() {
// The faster protocols should support normal rust Read and Write
// interfaces, letting the user use utilities like io::copy and using
// non-blocking behaviour with Read::read and Write::write. This
// non-blocking behaviour could be facilitated by having all socket
// reading and writing on their own thread and using sync types to
// communicate.
let mut uart = Port::new("a").expect("a Port A object").into_uart().expect("a Uart Pin wrapper");
io::copy(&mut File::create("/app/my-file.txt").expect("my-file.txt opened"), &mut uart);
// Tessel
mod tessel {
pub struct Error {}
pub enum ErrorKind {}
impl Error {
pub fn kind(&self) -> ErrorKind {}
pub fn message(&self) -> &'static str {}
// The TesselInner type holds neccessary handles, like mutexes and other
// sync types to communicate with whatever holds the Sockets to communicate
// pwm_frequency info. Since TesselInner is only used for static method
// Tessel does not need to own it. A Tessel may not even exist if more
// specific constructors are used to get Leds, Ports, or Pins. In those
// cases a TesselInner instance will still be created if pwm_frequency is
// called.
struct TesselInner {}
pub struct Tessel {
pub led: Vec<Led>,
pub port: Ports,
impl Tessel {
// These constructors are all convenience methods for the encased types.
// The top one, Tessel, can only be instantiated if all LEDs and Ports
// are not currently owned. Otherwise it returns an Error. In the case
// some Led, Pin, or Port is owned the user needs to instantiate the
// specific object they need by a specific constructor and unwrap the
// object in case they receive an error because the object they want is
// in fact owned.
pub fn new() -> Result<Tessel> {}
// Like the Tessel::new, leds returns an error if any of the leds is
// owned.
pub fn leds() -> Result<Vec<Led>> {}
// Like the Tessel::new function, ports returns an error if any Port or
// Pin is owned.
pub fn ports() -> Result<Ports> {}
// pwm_frequency is a static function. As long as a Pin or Port is owned
// it should succeed.
pub fn pwm_frequency(freq) -> Result<()> {}
// LED
mod led {
// Owns any objects needed to communicate to the hardware LED resource. May
// only be owned by one Led instance. If another owns it, creating a new Led
// will return an error.
struct LedInner {}
impl LedInner {
// For testing a non public interface can let tests create LedInner
// instances holding objects other than what Led::new may use. In
// production Led::new can must use into_led so that it finds or creates
// the LedInner instance as a standard process.
fn into_led() -> Led {}
pub struct Led {
inner: LedInner,
// Return the LedInner instance to somewhere or reset whatever value lets a
// future Led::new call know that the object can be created.
impl Drop for Led {}
impl Led {
pub fn new(color, kind) -> Result<Led> {}
pub fn on(&mut self) -> Result<()> {}
pub fn off(&mut self) -> Result<()> {}
pub fn toggle(&mut self) -> Result<()> {}
// Blocking function. Returns a result of the value read for
// consistency.
pub fn read(&self) -> Result<usize> {}
// Blocking function. Writes a value to the hardware resource.
pub fn write(&mut self, value: usize) -> Result<()> {}
// Port
mod port {
// Wrap the pair of Ports used by the Tessel object like it is in Node.
pub struct Ports {
a: Port,
b: Port,
// Data used by a thread to write to a port stream. The UnixStream can be
// blocking or nonblocking. Blocking in a separate thread is easier of the
// two paths. Primarily with nonblocking we wouldn't have a good path
// currently to know when we can next write. We would just have to
// occasionally try and succeed or fail. With a blocking thread doing the
// writing we can send instructions to it and it'll be able to write them as
// fast as the thread executes and the underlying stream lets it. When
// nothing is being written it can lock on however it receives new
// instructions wasting no cpu resources until then.
struct PortWriteThread {}
// Data used by a thread to "read" from the port stream. This thread needs
// to communicate with the write thread. It sends read commands through it
// from the read thread so there is one source to queue pins waiting for
// feedback and queue instructions to be written. This may need to be two
// threads. One that queues up new commands and sends instructions to the
// write thread. And a second that does the actual reading.
// Instead of callbacks like in Node, Pins should operate in threads or
// tasks through libraries like futures and fibers. Calls to write or read a
// pin (or set of pins through i2c, uart, and spi protocols) send commands
// to the appropriate PortThread (digital, interrupt, analog) and then block
// or fill a shared buffer (i2c, spi, uart) and block if that buffer fills
// up.
struct PortReadThread {}
struct PortInner {}
pub struct Port {
inner: PortInner,
pub pin: Vec<Pin>,
// Return the PortInner instance or reset whatever value lets future
// Port::new calls discover if they can be created.
impl Drop for Port {}
impl Port {
pub fn new(name: &'static str) -> Result<Port> {}
// Pin
mod pin {
// Hold objects to communicate with its PortStreams. If all of the PinInner
// objects are for a port are not owned, that port is turned off. And the
// reverse if any pin is owned, the port is turned on.
struct PinInner {}
pub struct Pin {
inner: PinInner,
impl Pin {
pub fn new(port_name: &'static str, index: usize) -> Result<Pin> {}
pub fn port_name(&self) -> &'static str {}
pub fn port_index(&self) -> index {}
pub fn is_digital(&self) -> bool {}
pub fn is_interrupt(&self) -> bool {}
pub fn is_analog(&self) -> bool {}
pub fn is_analog_read(&self) -> bool {}
pub fn is_analog_write(&self) -> bool {}
pub fn is_pwm(&self) -> bool {}
pub fn is_i2c(&self) -> bool {}
pub fn is_uart(&self) -> bool {}
pub fn is_spi(&self) -> bool {}
// Digital
mod digital {
// This is the first pin protocol in this document. The general idiom
// established here is that Pins know what they are but do not expose
// methods to perform work with a certain protocol. Since the multi pin
// protocols should own the pins so should the single pin protocols. Since
// all the protocols own the pin, the Pin type itself should give up all
// methods for communication to the respective protocols. This helps build
// a consistent interface for using pins to communicate with components of a
// project.
// With that established we need interfaces to convert pins into pin owning
// protocol types. The first two are below and are repeated for other
// protocols. The first creates a type that allows temporary access to the
// pin. Both have the same costs, but the first lets helper functions
// receive references to pins if not the protocol they need. Otherwise a
// helper function would need to own the relevant pin and return it or drop
// it. The second lets us convert into permanent protocol objects that can
// be returned from other functions or give a better sense of permanence in
// containing types.
// Return a temporary reference to a digital protocol holding a reference to
// a pin or return an error if the pin doesn't support digital (not possible
// for digital, but again this is for consistency of the api).
pub trait RefDigital {
fn digital<'a>(self) -> Result<DigitalRef<'a>> {}
// Return a digital protocol object that owns the underlying pin.
pub trait IntoDigital {
fn into_digital(self) -> Result<Digital> {}
// References to a Port can create a digital reference. As part of the api
// it will always use the same pin, like for example pin 0. If that pin has
// been pulled out of the Port object an error will be returned.
impl RefDigital for &mut Port {}
// A port itself can be converted into a digital protocol. It consumes the
// port and all other pins are dropped. As part of the api it will always
// use the same pin, like for example pin 0.
impl IntoDigital for Port {}
// One of the straight forward ones. Turn a Pin reference into a Digital
// reference.
impl RefDigital for &mut Pin {}
// The other straight forward one. Turn a Pin into a Digital object.
impl IntoDigital for Pin {}
// We can provide a lot of convenience implementations too. Like for
// iterators over pins taking the first item and erroring if the iterator is
// already at the end.
impl<T> RefDigital for T where T : Iterator<Item=&mut Pin> {}
impl<T> IntoDigital for T where T : Iterator<Item=Pin> {}
// A private trait. DigitalRef and Digital can implement this exposing a Pin
// reference. With this trait to get the reference another trait can
// implement all the methods for Digital once since it'll go through internal
// details the Pin has. This is all about the internal implementation.
trait BorrowDigitalPin {
fn borrow_pin(&mut self) -> &mut Pin;
// A public trait with the commands a Digital sends.
pub trait DigitalCommands : BorrowDigitalPin {
// Read the pin receiving a 0 or 1. This method is blocking.
fn read(&mut self) -> Result<usize>;
// Write a 0 or 1 to the pin. This method is blocking.
fn write(&mut self, value: usize) -> Result<()>;
// The generic implementation of DigitalCommands for anything implementing
// the private trait that lets us borrow the pin to access private internal
// details to read and write on the Digital. This way we only implement the
// methods once for a digital reference of pin owning digital. With this
// being a trait users or downstream libraries could do further wrapping and
// expose the same interface. Functions instead of needing a Digital or
// DigitalRef specifically can take a &DigitalCommands &mut DigitalCommands
// or Box<DigitalCommands>.
impl<T> DigitalCommands for T where T : BorrowDigitalPin {}
// The Digital refence object. Instead of owning a Pin it owns a mutable
// reference to a Pin.
struct DigitalRef<'a> {
pin: &'a mut Pin,
// The Digital object.
struct Digital {
pin: Pin,
// Implement the borrow trait letting Digital ref implement the methods for
// reading and writing the pin.
impl<'a> BorrowDigitalPin for DigitalRef<'a> {}
// Implement the borrow trait letting Digital implement the methods for
// reading and writing the pin.
impl BorrowDigitalPin for Digital {}
impl<'a> DigitalRef<'a> {
pub fn pin(&mut self) -> &mut Pin {}
impl Digital {
// Along with the trait methods, Digital also lets you borrow the
// internal pin normally or turn it back into the pin. These methods
// cannot error or panic since the Pin is the more abstract object that
// is required to even make Digital. We already know that we can safely
// return these values.
pub fn pin(&mut self) -> &mut Pin {}
pub fn into_pin(self) -> Pin {}
// Interrupt
mod interrupt {
// Interrupt follows the idioms for single pin protocols laid out by Digital.
pub trait RefInterrupt {
fn interrupt<'a>(&mut self) -> InterruptRef<'a>;
pub trait IntoInterrupt {
fn into_interrupt(self) -> Interrupt;
impl RefInterrupt for &mut Port {}
impl IntoInterrupt for Port {}
impl RefInterrupt for &mut Pin {}
impl IntoInterrupt for Pin {}
trait BorrowInterruptPin {
fn borrow_pin(&mut self) -> &mut Pin;
// Each of these methods is blocking and waits for some interrupt change.
// They don't repeatedly wait, each time you want to wait on the interrupt,
// you need to call the corresponding wait again.
trait InterruptCommands {
// Wait for the pin to be read as 1.
fn wait_rise(&mut self) -> Result<()>;
// Wait for the pin to be read as 0.
fn wait_fall(&mut self) -> Result<()>;
// Wait for the pin to be a different value. The value is returned.
fn wait(&mut self) -> Result<bool>;
impl<T> InterruptCommands for T where T : BorrowInterruptPin {}
pub struct InterruptRef<'a> {}
pub struct Interrupt {}
impl<'a> BorrowInterruptPin for InterruptRef<'a> {}
impl BorrowInterruptPin for Interrupt {}
// Analog
mod analog {
// To keep protocol types from being to complicated and letting Analog be
// thought of like other protocols it has read and write methods. Turning a
// pin into a Analog object only requires that the pin can be read. Pin
// writability is only checked when actually writing to the pin. If the pin
// can not be written to the call will error.
pub trait RefAnalog {
fn analog<'a>(self) -> Result<AnalogRef<'a>> {}
pub trait IntoAnalog {
fn into_analog(self) -> Result<Analog> {}
impl RefAnalog for &mut Port {}
impl IntoAnalog for Port {}
impl RefAnalog for &mut Pin {}
impl IntoAnalog for Pin {}
trait BorrowAnalogPin {
fn borrow_pin(&mut self) -> &mut Pin;
pub trait AnalogCommands {
// Read an analog pin returning its value. This method is blocking.
fn read(&mut self) -> Result<f32> {}
// Write a value to an analog pin. If the pin cannot be written to it
// will error. This method is blocking.
fn write(&mut self, value: f32) -> Result<()> {}
impl<T> AnalogCommands for T where T : BorrowAnalogPin {}
pub struct AnalogRef<'a> {}
impl BorrowAnalogPin for AnalogRef {}
pub struct Analog {}
impl BorrowAnalogPin for Analog {}
// PWM
mod pwm {
pub trait RefPwm {
fn pwm<'a>(self) -> Result<PwmRef<'a>> {}
pub trait IntoPwm {
fn into_pwm(self) -> Result<Pwm> {}
impl RefPwm for &mut Port {}
impl IntoPwm for Port {}
impl RefPwm for &mut Pin {}
impl IntoPwm for Pin {}
trait BorrowPwmPin {
fn borrow_pin(&mut self) -> &mut Pin;
pub trait PwmCommands {
// Change the duty_cycle for this pin. This method is not blocking.
fn duty_cycle(&mut self, cycle: f32) -> Result<()>;
impl<T> PwmCommands for T where T : BorrowPwmPin {}
impl<T> Drop for T where T : BorrowPwmPin {}
struct PwmRef<'a> {
pin: &'a mut Pin,
impl<'a> BorrowPwmPin for PwmRef<'a> {}
struct Pwm {
pin: Pin,
impl BorrowPwmPin for Pwm {}
impl Pwm {
pub fn pin(&mut self) -> &mut Pin {}
pub fn into_pin(self) -> Pin {}
// Transfer
mod transfer {
// Two multi pin protocols share an idea, transfering data, that is reading
// and writing blocks of data at the same time. For rust this is like
// combining Write::write_all and Read::read_exact together, leading this
// design to be blocking while I2C, SPI, and UART should otherwise be
// possible to not block when Write::write and Read::read are used (unless
// their underlying buffer is filled).
pub trait Transfer {
fn transfer(&mut self, write: &[u8], read: &mut [u8]) -> Result<()>;
// I2C
mod i2c {
// I2c is the first multi pin protocol in this document. Multi pin protocols
// follow most of the same ideas the single pin protocols do. The biggest
// differences are that these protocols take multiple pins and implement
// Rust's std Read and Write traits. As those traits can be (but don't
// guarentee) non-blocking, multiple messages can be queued up depending on
// the underlying way those messages are passed to the co-processor.
// Since I2c and Spi can talk to multiple devices their interfaces are
// further wrapped by Slave types. These Slave types are like Rust's Arc
// referring to the same underlying objects to communicate with the
// co-processor and be on different separate threads. While they hold those
// references the original pins cannot be reused. Instead of blocking
// dropping, which could create race conditions, those pins cannot be turned
// into another protocol other than I2c until all slaves are also dropped. A
// pin in use by an I2c slave will return an error when trying to turn it
// into any protocol other than I2c.
// Not being able to turn into a protocol because its in use by a slave does
// make this interface a little awkward. This is about finding a happy
// middle ground. One option for this API would be slaves with lifetimes.
// Those slaves could exist as long as the I2c type, meaning they can only
// live in the same scope. This would exclude sending slaves to different
// threads to communicate with devices. A second option would be blocking at
// some point. We could block on dropping the I2c object. We could block
// when trying to turn the pin into a communications object. Both blocking
// options can easily end up in deadlock scenarios if the same thread owns
// one of the slaves. The third option is this one. Slaves can be sent to
// other threads and you can't easily deadlock the thread. A way to make
// this last option less awkward will be to have a method on Pin that can in
// its name and documentation blocks until the Pin can be turned into a
// given type. Otherwise users can poll occasionally to see if the Pin can
// be turned into a given protocol.
// Non-blocking writing with a supporting mechanism underneath is easy. Send
// the message to be written through a buffer until the buffer is filled.
// Once filled we should block until we can write more.
// Non-blocking reading is more tricky. (This does not apply to Uart.) Since
// I2c and Spi read by instruction to their devices the easy option for this
// API would be to be blocking. We can present a non-blocking interface by
// taking a read call as instruction to send a rx command if one currently
// isn't being fulfilled. After that command is sent, any further reads can
// return any data recevied from the co-processor until the next read is
// made with the buffer empty and the last command being fulfilled.
pub trait RefI2c {
fn i2c(self) -> Result<I2cRef>;
pub trait IntoI2c {
fn into_i2c(self) -> Result<I2c>;
// These are some examples. We could also support Iterator of &mut Pin and
// Pin or Vec or slices of the same.
impl RefI2c for &mut Port {}
impl RefI2c for (&mut Pin, &mut Pin) {}
impl IntoI2c for Port {}
impl IntoI2c for (Pin, Pin) {}
trait BorrowI2cPin {
fn borrow_pin(&mut self) -> &mut Pin;
impl<T> io::Read for T where T : BorrowI2cPin {}
impl<T> io::Write for T where T : BorrowI2cPin {}
impl<T> Transfer for T where T : BorrowI2cPin {}
trait SlaveOf {
type Slave;
// Up to the addressable limit number of slaves can exist. But no two
// may share the same address.
fn slave(&self, addr: u8) -> Result<Self::Slave>;
pub struct I2cRef<'a> {}
pub struct I2cRefSlave<'a> {}
impl SlaveOf for I2cRef {}
pub struct I2c {}
pub struct I2cSlave {}
impl SlaveOf for I2c {}
impl I2c {
pub fn into_pins(self) -> (Pin, Pin) {}
impl Drop for I2cRef {}
impl BorrowI2cPin for I2cRefSlave {}
impl Drop for I2c {}
impl BorrowI2cPin for I2cSlave {}
mod uart {
// Uart is similar to I2c. It uses multiple pins. It can Read and Write.
// Since it can only communicate with one device it doesn't need to lock
// pins like I2c slaves. Reading a Uart is easier since its delivered
// asynchrounously instead of on demand like I2c and Uart. If on read
// nothing has yet been delivered by Uart, read will immediately return.
pub trait RefUart {
fn uart(&mut self) -> Result<RefUart<'a>>;
pub trait IntoUart {
fn into_uart(self) -> Result<Uart>;
impl RefUart for &mut Port {}
impl RefUart for (&mut Pin, &mut Pin) {}
impl IntoUart for Port {}
impl IntoUart for (Pin, Pin) {}
trait BorrowUartPin {
fn borrow_uart_pin(&mut self) -> &mut Pin;
impl<T> Read for T where T : BorrowUartPin {}
impl<T> Write for T where T : BorrowUartPin {}
pub struct UartRef<'a> {}
impl BorrowUartPin for UartRef {}
pub struct Uart {}
impl BorrowUartPin for Uart {}
// SPI
mod spi {
pub trait RefSpi {
fn spi(&mut self) -> Result<SpiRef>;
pub trait IntoSpi {
fn into_spi(self) -> Result<Spi>;
impl RefSpi for Port {}
impl RefSpi for (&mut Pin, &mut Pin, &mut Pin) {}
impl IntoSpi for Port {}
impl IntoSpi for (Pin, Pin, Pin) {}
trait BorrowSpiPin {
fn borrow_pin(&mut self) -> &mut Pin;
impl<T> io::Read for T where T : BorrowSpiPin {}
impl<T> io::Write for T where T : BorrowSpiPin {}
impl<T> Transfer for T where T : BorrowSpiPin {}
trait SlaveOf {
type Slave;
fn slave(&self, chip_select: Pin) -> Result<Self::Slave>;
pub struct SpiRef<'a> {}
pub struct SpiRefSlave<'a> {}
pub struct Spi {}
pub struct SpiSlave {}
impl SlaveOf for SpiRef {}
impl SlaveOf for Spi {}
impl BorrowSpiPin for SpiRefSlave {}
impl BorrowSpiPin for SpiSlave {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.