| /// USB Device Class for I2C transactions. |
| // |
| // It's not very good, and the API is weird. Someone with more USB device design experience could |
| // easily come up with something better. |
| // |
| // Control OUT transactions are used to perform I2C transfers to/from an internal buffer. |
| // Bulk IN/OUT transactions are used to transfer contents of the buffer to the host. It has not |
| // been optimized for speed or pipelining. |
| // |
| // To perform an I2C read: |
| // 1) Control OUT: ReadI2C(Address: 0xAA, Length: N) |
| // (0xAA is the device address, N is the amount of bytes to read. Cannot be larger than |
| // BUFFER_SIZE). |
| // This performs an I2C read of N bytes into the inne buffer of the device, starting at |
| // address 0. |
| // 2) Control IN: GetStatus() |
| // The host ensures that the transaction was either ACK or NACK by getting one byte of status |
| // from the device. |
| // 3) Control OUT: ReadBuffer(Address: X, Length: N) |
| // (X is the address within the buffer, N is the amount of bytes to transfer to the host. N |
| // cannot be larger than PACKET_SIZE). |
| // 4) Bulk IN: Read PACKET_SIZE bytes. |
| // Steps 3/4 can be skipped for scanning (the device won't mind the inner buffer not being read). |
| // |
| // To perform an I2C write: |
| // 1) Control OUT: SetWritePointer(Addrss: X) |
| // 2) Bulk OUT: Write at most PACKET_SIZE bytes. |
| // Repeat steps 1/2 to fill buffer with an I2c transaction. |
| // 3) Control OUT: WriteI2C(Address: 0x00, Length: N) |
| // (0xAA is the device address, N is the amount of bytes to write. Cannot be larger than |
| // BUFFER_SIZE). |
| // 4) Control IN: GetStatus() |
| // The host ensures that the transaction was either ACK or NACK by getting one byte of status |
| // from the device. |
| |
| use embedded_hal::digital::v2::OutputPin; |
| use usb_device::class_prelude::*; |
| use nb::Error as NbError; |
| use stm32f1xx_hal::{ |
| gpio::{gpiob::*, Alternate, OpenDrain}, |
| i2c::{BlockingI2c, Error as I2CError}, |
| pac::I2C1, |
| prelude::*, |
| }; |
| |
| use num_derive::FromPrimitive; |
| use num_traits::FromPrimitive; |
| |
| use crate::{hprint, hprintln}; |
| |
| // Size of buffer within class, in bytes. Dictates maximum I2C transaction size. |
| const BUFFER_SIZE: usize = 1024; |
| // Size of bulk packets. |
| const PACKET_SIZE: usize = 64; |
| |
| // All IN/OUT references bellow conform to typical USB naming, where IN: from device to host; OUT: |
| // from host to device. |
| |
| /// Request number passed within Control IN requests to the I2C interface (ie. 'gets' from device). |
| #[derive(FromPrimitive)] |
| #[repr(u8)] |
| enum ControlInRequest { |
| /// Write the current status as a single byte in response. |
| GetStatus = 1, |
| } |
| |
| /// Request number passed within Control OUT requests to the I2C interface (ie. 'sets' from the |
| /// host). |
| #[derive(FromPrimitive)] |
| #[repr(u8)] |
| enum ControlOutRequest { |
| /// Set LED on or off (value == 0 -> off; on otherwise). |
| SetLED = 1, |
| |
| /// Perform I2C bus read of a given length from a given I2C address. |
| /// I2C Address: lower 8 bits of value. |
| /// Read Length: upper 8 bits of value. |
| ReadI2C = 2, |
| |
| /// Schedule a BULK IN transaction on the USB bus with the contents of the inner buffer. |
| /// Buffer start address: lower 8 bits of value |
| /// Read Length: upper 8 bits of value. |
| ReadBuffer = 3, |
| |
| /// Perform I2C bus write of a given length to a given I2C address. |
| /// I2C Address: lower 8 bits of value. |
| /// Read Length: upper 8 bits of value. |
| WriteI2C = 4, |
| |
| /// Set inner buffer write pointer. Any subsequent BULK OUT will write to the buffer at that |
| /// address (but will not auto advance the pointer). |
| SetWritePointer = 5, |
| } |
| |
| /// Status of the I2C class. Combines information about requested transactions and I2C bus |
| /// responses. |
| #[derive(Copy, Clone)] |
| #[repr(u8)] |
| enum Status { |
| /// Last request okay. |
| OK = 0, |
| /// Last request contained an invalid argument. |
| InvalidArgument = 1, |
| /// Last request okay, resulted in a successful I2C transaction. |
| Ack = 2, |
| /// Last request okay, resulted in a NACKd I2C transaction. |
| Nack = 3, |
| /// Last request okay, resulted in a fully failed I2C transaction. |
| BusError = 4, |
| } |
| |
| pub struct I2CClass<'a, B: UsbBus, LED> { |
| interface: InterfaceNumber, |
| /// Bulk IN endpoint for buffer transfers to host. |
| ep_in: EndpointIn<'a, B>, |
| /// Bulk OUT endpoint for buffer transfers from host. |
| ep_out: EndpointOut<'a, B>, |
| |
| /// LED used for debugging. |
| led: LED, |
| |
| /// The underlying I2C device. |
| i2c_dev: BlockingI2c<I2C1, (PB6<Alternate<OpenDrain>>, PB7<Alternate<OpenDrain>>)>, |
| |
| /// Marker that is true when the host requested a BULK OUT via ReadBuffer. |
| expect_bulk_out: bool, |
| |
| /// The underlying buffer and its write pointer. |
| buffer: [u8; BUFFER_SIZE], |
| write_pointer: usize, |
| |
| /// The device's main status byte, used by host to check whether operations were succesful. |
| status: Status, |
| } |
| |
| impl<B: UsbBus, LED: OutputPin> I2CClass<'_, B, LED> { |
| pub fn new( |
| alloc: &UsbBusAllocator<B>, |
| led: LED, |
| i2c_dev: BlockingI2c<I2C1, (PB6<Alternate<OpenDrain>>, PB7<Alternate<OpenDrain>>)>, |
| ) -> I2CClass<'_, B, LED> { |
| I2CClass { |
| interface: alloc.interface(), |
| ep_in: alloc.bulk(PACKET_SIZE as u16), |
| ep_out: alloc.bulk(PACKET_SIZE as u16), |
| led, i2c_dev, |
| |
| expect_bulk_out: false, |
| |
| buffer: [0; BUFFER_SIZE], |
| write_pointer: 0usize, |
| status: Status::OK, |
| } |
| } |
| } |
| |
| impl<'a, B: UsbBus, LED: OutputPin> UsbClass<B> for I2CClass<'a, B, LED> { |
| fn reset(&mut self) { |
| self.expect_bulk_out = false; |
| self.status = Status::OK; |
| } |
| |
| fn control_in(&mut self, xfer: ControlIn<B>) { |
| let req = xfer.request(); |
| |
| if req.request_type != control::RequestType::Vendor |
| || req.recipient != control::Recipient::Interface |
| || req.index != u8::from(self.interface) as u16 { |
| return |
| } |
| |
| match FromPrimitive::from_u8(req.request) { |
| /// Serve GetStatus: return this.status. |
| Some(ControlInRequest::GetStatus) => { |
| let status = self.status.clone() as u8; |
| xfer.accept(|buf| { |
| buf[0] = status; |
| Ok(1usize) |
| }).ok(); |
| }, |
| _ => { |
| hprintln!("Unhandled control in on iface: {:?}", req).unwrap(); |
| }, |
| } |
| } |
| |
| fn control_out(&mut self, xfer: ControlOut<B>) { |
| let req = xfer.request(); |
| |
| if req.request_type != control::RequestType::Vendor |
| || req.recipient != control::Recipient::Interface |
| || req.index != u8::from(self.interface) as u16 { |
| return |
| } |
| |
| match FromPrimitive::from_u8(req.request) { |
| // Serve SetLED. |
| Some(ControlOutRequest::SetLED) => { |
| let on: bool = req.value > 0; |
| match on { |
| true => self.led.set_low(), |
| false => self.led.set_high(), |
| }.ok(); |
| xfer.accept().ok(); |
| }, |
| |
| // Serve ReadI2C: read len bytes from I2C addr into internal buffer. |
| Some(ControlOutRequest::ReadI2C) => { |
| let addr: u8 = (req.value & 0xff) as u8; |
| let len: u8 = (req.value >> 8) as u8; |
| if len as usize > BUFFER_SIZE || len < 1u8 { |
| self.status = Status::InvalidArgument; |
| xfer.accept().ok(); |
| return |
| } |
| if addr > 127u8 { |
| self.status = Status::InvalidArgument; |
| xfer.accept().ok(); |
| return |
| } |
| match self.i2c_dev.read(addr, &mut self.buffer[0usize..(len as usize)]) { |
| Ok(_) => { |
| self.status = Status::Ack; |
| }, |
| Err(NbError::Other(I2CError::Acknowledge)) => { |
| self.status = Status::Nack; |
| }, |
| Err(e) => { |
| hprintln!("When reading I2C (addr {}, {} bytes): {:?}", addr, len, e).ok(); |
| self.status = Status::BusError; |
| }, |
| } |
| xfer.accept().ok(); |
| }, |
| |
| // Serve ReadBuffer: send BULK IN with slice of buffer. |
| Some(ControlOutRequest::ReadBuffer) => { |
| let addr: u8 = (req.value & 0xff) as u8; |
| let len: u8 = (req.value >> 8) as u8; |
| |
| if len as usize > PACKET_SIZE || len < 1u8 { |
| self.status = Status::InvalidArgument; |
| xfer.accept().ok(); |
| return |
| } |
| |
| let start = addr as usize; |
| let end = (addr + len) as usize; |
| if end as usize > BUFFER_SIZE { |
| self.status = Status::InvalidArgument; |
| xfer.accept().ok(); |
| return |
| } |
| |
| hprintln!("READ BUFFER, addr: {}, len: {}", addr, len).ok(); |
| |
| self.status = Status::OK; |
| xfer.accept().ok(); |
| match self.ep_in.write(&self.buffer[start..end]) { |
| Ok(count) => { |
| }, |
| Err(UsbError::WouldBlock) => {}, |
| Err(err) => { |
| hprintln!("bulk write failed: {:?}", err).ok(); |
| }, |
| } |
| }, |
| |
| // Serve WriteI2C: write len bytes to I2C bus at addr from internal buffer. |
| Some(ControlOutRequest::WriteI2C) => { |
| let addr: u8 = (req.value & 0xff) as u8; |
| let len: u8 = (req.value >> 8) as u8; |
| if len as usize > BUFFER_SIZE || len < 1u8 { |
| self.status = Status::InvalidArgument; |
| xfer.accept().ok(); |
| return |
| } |
| if addr > 127u8 { |
| self.status = Status::InvalidArgument; |
| xfer.accept().ok(); |
| return |
| } |
| |
| hprintln!("WRITE I2C, addr: {}, len: {}", addr, len).ok(); |
| match self.i2c_dev.write(addr, &self.buffer[0usize..(len as usize)]) { |
| Ok(_) => { |
| self.status = Status::Ack; |
| }, |
| Err(NbError::Other(I2CError::Acknowledge)) => { |
| self.status = Status::Nack; |
| }, |
| Err(e) => { |
| hprintln!("When writing I2C (addr {}, {} bytes): {:?}", addr, len, e).ok(); |
| self.status = Status::BusError; |
| }, |
| } |
| xfer.accept().ok(); |
| }, |
| |
| // Serve SetWritePointer: set start address at which bytes from a BULK OUT will be |
| // written to. The write pointer does _not_ increment on every write, so will need to |
| // be manually controler after every BULK transfer. |
| Some(ControlOutRequest::SetWritePointer) => { |
| let pointer = req.value; |
| if (pointer as usize) >= BUFFER_SIZE { |
| self.status = Status::InvalidArgument; |
| xfer.accept().ok(); |
| return |
| } |
| hprintln!("SET WRITE PTR, pointer: {}", pointer).ok(); |
| self.write_pointer = pointer as usize; |
| self.status = Status::OK; |
| xfer.accept().ok(); |
| }, |
| _ => { |
| hprintln!("Unhandled control out on iface: {:?}", req).ok(); |
| }, |
| } |
| } |
| |
| fn get_configuration_descriptors( |
| &self, |
| writer: &mut DescriptorWriter, |
| ) -> usb_device::Result<()> { |
| writer.interface( |
| self.interface, |
| 0xff, |
| 21, 37, |
| )?; |
| writer.endpoint(&self.ep_in)?; |
| writer.endpoint(&self.ep_out)?; |
| |
| Ok(()) |
| } |
| |
| fn poll(&mut self) { |
| let mut temp_buf = [0; PACKET_SIZE]; |
| // Serve BULK OUT writes - copy bytes into internal buffer. |
| match self.ep_out.read(&mut temp_buf) { |
| Ok(count) => { |
| if self.expect_bulk_out { |
| self.expect_bulk_out = false; |
| } else { |
| panic!("unexpectedly read data from bulk out endpoint"); |
| } |
| hprintln!("SET BUFFER: ptr {}, {} bytes", self.write_pointer, count).ok(); |
| for (i, c) in temp_buf.iter().enumerate() { |
| let ptr = self.write_pointer + i; |
| // Silently drop bytes that do not fit in buffer. |
| if ptr >= BUFFER_SIZE { |
| continue; |
| } |
| self.buffer[ptr] = c.clone(); |
| } |
| }, |
| Err(UsbError::WouldBlock) => {}, |
| Err(err) => panic!("bulk read {:?}", err), |
| } |
| } |
| |
| fn endpoint_out(&mut self, addr: EndpointAddress) { |
| if addr == self.ep_out.address() { |
| self.expect_bulk_out = true; |
| } |
| } |
| |
| fn endpoint_in_complete(&mut self, addr: EndpointAddress) { |
| if addr == self.ep_in.address() { |
| // TODO(q3k): should we be doing something here? |
| } |
| } |
| } |