layout: true class: middle, inverse --- class: middle, center, inverse # How to make your own Smart Watch ## Using the `device-driver` crate to integrate touch screen functionality --- class: middle, left # Agenda 1. Introduction 2. Background: Embedded Rust & Device Drivers 3. Implementation Walkthrough 4. Challenges & Lessons Learned 5. Conclusion 6. ~~Live Demo~~ | Demo in the back room at lunch --- class: middle, center, inverse # Introduction --- class: middle, left # Introduction **Anders Rasmussen**——**@voided@hachyderm.io** - Software Engineer since 2007 (professionally) - Tinkerer since childhood - Tragic Project Starter --- class: middle, left # Introduction **Anders Rasmussen**——**@voided@hachyderm.io** - Rust since 2017 - Embedded Rust since 2020 - Pandemic Hobby --- class: middle, center # Yes - it was mechanical keyboards --- class: middle, center, inverse # Background --- # Why this presentation? - More than just a blinky example - Give tools to help make better hardware integrations --- # Why Embedded Rust? - Memory safety - Strong type system for safer APIs - Growing ecosystem and community support --- # Why Write a Device Driver? - Directly interface with hardware - Improve performance and control - Provide a safe and convenient programming interface --- # The `device-driver` Crate - Provides a framework for writing device drivers in Rust - Abstracts common driver tasks - Encourages safety and modularity --- # Target Device: Hynitron CST816S ### Features: - High-performance self-capacitance touch chip - >100Hz touch reporting frequency in Dynamic Mode - Low power consumption: - Dynamic Mode: <1.6mA - Standby Mode: <6.0µA - Sleep Mode: <1.0µA ??? [ datasheet ](./assets/embedded-driver/CST816S_Datasheet_EN.pdf) --- # Target Device: Hynitron CST816S ### Communication: - I²C interface (10KHz-400KHz) - Interrupt Request (IRQ) pin for event signaling ??? [registers](./assets/embedded-driver/CST816S_register_declaration.pdf) ---  --- # Implementation Test Device Waveshare RP2040 1.28 Round LCD Touch **Pros:** - Affordable - Feature rich - Small **Cons:** - Limited debugging capability --- class: middle, center, inverse # Implementation Walkthrough --- # Implementing the Driver - Using DSL macro `device_driver::create_device!` --- # Using the `device-driver` DSL ## Global Config ```rust device_driver::create_device!( device_name: CST816S, // Public name dsl: { config { // We know this from the Register Specification type RegisterAddressType = u8; } [ registers ... ] [ commands ... ] [ buffers ... ] } ); ``` --- ## Simple register definition ```rust device_driver::create_device!( dsl: { config: { ... } register ChipId { type Access = RO; // Access Type: RO, WO, RW const ADDRESS = 0xA7; // Register address from spec const SIZE_BITS = 8; // Reply value size value: uint = 0..8, // bits 0 to 8 (exluding 8) as uint } } ); ``` --- ## Custom Register Datatype ```rust device_driver::create_device!( dsl: { config: { ... } register GestureId { type Access = RO; const ADDRESS = 0x01; const SIZE_BITS = 8; value: uint as try enum Gesture { NoGesture = 0x00, SlideUp = 0x01, SlideDown = 0x02, SlideLeft = 0x03, SlideRight = 0x04, SingleClick = 0x05, DoubleClick = 0x0B, LongPress = 0x0C, } = 0..8, } } ); ``` ??? Convert each value to a enum variant Needs to be fallible since the values have gaps --- # Custom Conversion Types ## DSL Definition ```rust register IrqPulseWidth { ... value: uint as PulseWidth = 0..8 } ``` --- # Custom Conversion Types ## Implementation ```rust #[derive(Debug)] pub struct PulseWidth { value: u8, } impl PulseWidth { pub fn new(value: u8) -> Self { debug_assert!(value > 0); debug_assert!(value <= 200); Self { value } } } ``` --- # Custom Conversion Types ```rust impl From
for PulseWidth { fn from(value: u8) -> Self { dbg_assert!(value > 0); dbg_assert!(value <= 200); Self { value } } } impl From
for u8 { fn from(value: PulseWidth) -> Self { *value } } ``` --- # What is a Bus? - A communication system that transfers data between components - Can be parallel (multiple data wires) or serial (single data wire) - Examples: PCI, SPI, I²C, UART --- # What is I²C? - Stands for **Inter-Integrated Circuit** - A serial communication protocol for low-speed peripherals - Uses two lines: - **SCL (Serial Clock Line)** - **SDA (Serial Data Line)** - Supports multiple devices on the same bus - Differentiated with device address - Communication always initiated by the controller node --- # Integrating I²C Communication - Implement the `RegisterInterface` trait for I²C communication - Utilize the `embedded-hal` crate for hardware abstraction - Ensure safe and efficient data transfer between the driver and device --- # Creating a Low-level Driver Interface Generic over the communication protocol ```rust pub(crate) struct DeviceInterface
{ device_address: SevenBitAddress, i2c: I2C, } impl
DeviceInterface
{ pub(crate) const fn new(i2c: I2C, device_address: SevenBitAddress) -> Self { Self { i2c, device_address, } } } ``` --- # Implementing I²C Read Function ```rust impl
RegisterInterface for DeviceInterface
{ type Error = DeviceError
; type AddressType = u8; fn read_register( &mut self, address: Self::AddressType, _size_bits: u32, data: &mut [u8], ) -> Result<(), Self::Error> { self.i2c.write_read(self.device_address, &[address], data)?; Ok(()) } } ``` Or `embedded_hal_async`, `AsyncRegisterInterface` for async implementations ??? The implementation of DeviceError can be found in the example repo --- # Implementing I²C Write Function ```rust impl
RegisterInterface for DeviceInterface
{ type Error = DeviceError
; type AddressType = u8; fn write_register( &mut self, address: Self::AddressType, _size_bits: u32, data: &[u8], ) -> Result<(), Self::Error> { self.i2c.transaction( self.device_address, &mut [Operation::Write(&[address]), Operation::Write(data)], )?; Ok(()) } } ``` --- # Handling Errors in I²C Communication - Use Rust’s `Result` type for error handling - Implement meaningful error messages for debugging - Ensure graceful fallback in case of failures --- # High-level Driver We're now into the territory of opinions of how to use the driver I decided to spend a bit of time writing an `event` method, that reads gesture events --- ## Read a single touch event ### Public interface struct ```rust /// Public interface struct for our High-level driver pub struct CST816S
{ device: Device
>, interrupt_pin: TPINT, reset_pin: TPRST, } ``` --- ## Read a single touch event ```rust impl
CST816S
where I2C: I2c, TPINT: InputPin, TPRST: OutputPin, { pub fn event(&mut self) -> Option
{ if self.interrupt_pin.is_high().unwrap() { return None; } let x = self.device.xpos().read().unwrap().value(); let y = self.device.ypos().read().unwrap().value(); let gesture = self.device.gesture_id().read().unwrap().value(); let point: Point = (x, y); Some(TouchEvent { point, gesture, }) } } ``` --- # Live Demonstration - Running the driver on real hardware - Observing touch inputs and responses --- class: middle, center, inverse # Challenges & Lessons Learned --- class: middle, center # Challenges & Lessons Learned --- class: middle, center # Challenges & Lessons Learned ### Handling incomplete or unclear device documentation ### Leveraging community resources and prior implementations --- class: middle, center # Challenges & Lessons Learned ### Translating register definitions into Rust's type system ### Tedious but a lot less boilerplate than a traditional approach --- class: middle, center # Challenges & Lessons Learned ### Balancing safety and performance in embedded contexts ### Can focus on the public API instead --- class: middle, center # Challenges & Lessons Learned ### Difficulty debugging the test device ### rp2040 doesn't support `probe-rs` logging without a debug probe --- class: middle, center # Conclusion --- # Conclusion - Rust offers a robust platform for embedded device drivers - The `device-driver` crate streamlines low-level hardware interactions - Continuous learning and adaptation are key in embedded development - Make sure development hardware has proper debugging output - `probe-rs` and `defmt` are valuable crates for embedded development --- # Q&A & Further Discussion ## Any questions? _(Live demonstration continues if time permits)_