gt9x 0.1.0

A no_std driver for the GT9x series of capacitive touch screen controllers, supporting both async and blocking interfaces.
Documentation
use core::marker::PhantomData;
use embedded_hal::i2c;
use heapless::Vec;

use crate::{chip::*, reg};

/// Driver errors
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug)]
pub enum Error<I2cE> {
    /// I2C error
    I2C(I2cE),
    /// Invalid Data
    InvalidData,
    /// Invalid Chip
    InvalidChip,
    /// Invalid Product ID
    InvalidProductId,
}

/// Blocking GT9x driver
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Gt9x<'a, C, I2C>
where
    C: Chip,
    I2C: i2c::I2c,
{
    chip: PhantomData<C>,
    i2c: I2C,
    i2c_addr: u8,
    buf: &'a mut [u8],
}

impl<'a, C, I2C> Gt9x<'a, C, I2C>
where
    C: Chip,
    I2C: i2c::I2c,
{
    /// The default i2c address is 0x5D. If the chip requires a different address,
    /// it can be set using the `set_i2c_addr` method.
    /// Create a new GT9x driver.
    ///
    /// The buffer `buf` must be large enough to hold the touch data.
    /// The required size is `(MAX_POINTS * 8).max(4)`.
    pub fn new(i2c: I2C, buf: &'a mut [u8]) -> Self {
        let required_len = (C::MAX_POINTS as usize * 8).max(4);
        assert!(buf.len() >= required_len, "Buffer too small");
        Gt9x {
            chip: PhantomData,
            i2c,
            i2c_addr: 0x5D,
            buf,
        }
    }

    /// Set the I2C address of the touch screen controller.
    /// The default address is 0x5D.
    pub fn set_i2c_addr(&mut self, addr: u8) {
        self.i2c_addr = addr;
    }

    /// Write data to the touch screen controller.
    pub fn write(&mut self, write: &[u8]) -> Result<(), Error<I2C::Error>> {
        self.i2c.write(self.i2c_addr, write).map_err(Error::I2C)
    }

    /// Write data to and read data from the touch screen controller.
    pub fn write_read(&mut self, write: &[u8], len: usize) -> Result<(), Error<I2C::Error>> {
        self.i2c
            .write_read(self.i2c_addr, write, &mut self.buf[..len])
            .map_err(Error::I2C)?;
        Ok(())
    }

    /// Initialize the touch screen controller.
    /// This method will check the product ID of the chip.
    pub fn init(&mut self) -> Result<(), Error<I2C::Error>> {
        self.write(reg::CMD::ModeRead.as_bytes())?;
        self.write_read(reg::ID_ADDR, 4)?;
        match core::str::from_utf8(&self.buf[..4]) {
            Ok(product_id) => {
                #[cfg(feature = "defmt")]
                defmt::trace!("Product ID: {}", product_id);
                if product_id == C::ID {
                    self.write(reg::CLEAR_STATUS)?;
                    Ok(())
                } else {
                    #[cfg(feature = "defmt")]
                    defmt::error!("Expected: {}, found: {}", C::ID, product_id);
                    Err(Error::InvalidProductId)
                }
            }
            Err(_) => {
                #[cfg(feature = "defmt")]
                defmt::error!("InvalidData: {}", self.buf[..4]);
                Err(Error::InvalidData)
            }
        }
    }

    /// Get the current touch points.
    pub fn get_touches(
        &mut self,
    ) -> Result<Vec<Point, { C::MAX_POINTS as usize }>, Error<I2C::Error>>
    where
        [(); C::MAX_POINTS as usize]:,
    {
        self.write_read(reg::STATUS_ADDR, 1)?;
        let status = reg::Status::from_bits(self.buf[0]);

        if status.num_points() == 0 || !status.buffer_status() {
            self.write(reg::CLEAR_STATUS)?;
            return Ok(Vec::new());
        }

        self.write_read(&reg::POINT_BASE, status.num_points() as usize * 8)?;

        let mut points = Vec::new();
        for i in 0..status.num_points().min(C::MAX_POINTS) {
            let base = (i as usize) * 8;
            let point = Point {
                id: self.buf[base] & 0x0F,
                x: u16::from_le_bytes([self.buf[base + 1], self.buf[base + 2]]),
                y: u16::from_le_bytes([self.buf[base + 3], self.buf[base + 4]]),
                area: u16::from_le_bytes([self.buf[base + 5], self.buf[base + 6]]),
            };
            points.push(point).map_err(|_| Error::InvalidData)?;
        }

        self.write(reg::CLEAR_STATUS)?;
        Ok(points)
    }
}