/* * Copyright 2023 jacqueline * * SPDX-License-Identifier: GPL-3.0-only */ #include "drivers/touchwheel.hpp" #include #include #include "assert.h" #include "driver/gpio.h" #include "driver/i2c.h" #include "esp_err.h" #include "esp_log.h" #include "freertos/projdefs.h" #include "hal/gpio_types.h" #include "hal/i2c_types.h" #include "drivers/i2c.hpp" namespace drivers { // Touch wheel implementation using a Microchip AT42QT2120 [[maybe_unused]] static const char* kTag = "TOUCHWHEEL"; static const uint8_t kTouchWheelAddress = 0x1C; static const gpio_num_t kIntPin = GPIO_NUM_25; auto TouchWheel::isAngleWithin(int16_t wheel_angle, int16_t target_angle, int threshold) -> bool { int16_t difference = (wheel_angle - target_angle + 127 + 255) % 255 - 127; return difference <= threshold && difference >= -threshold; } TouchWheel::TouchWheel() { i2c_device_config_t config = { .dev_addr_length = I2C_ADDR_BIT_LEN_7, .device_address = kTouchWheelAddress, .scl_speed_hz = 400'000, .scl_wait_us = 0, .flags = {.disable_ack_check = false}, }; ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_handle(), &config, &i2c_)); gpio_config_t int_config{ .pin_bit_mask = 1ULL << kIntPin, .mode = GPIO_MODE_INPUT, .pull_up_en = GPIO_PULLUP_ENABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_DISABLE, }; gpio_config(&int_config); WriteRegister(RESET, 1); vTaskDelay(pdMS_TO_TICKS(300)); // Configure keys 0, 1, and 2 as a wheel. WriteRegister(SLIDER_OPTIONS, 0b11000000); // Configure adjacent key suppression. // Wheel keys. Set to channel 1. WriteRegister(Register::KEY_CONTROL_BASE + 0, 0b100); WriteRegister(Register::KEY_CONTROL_BASE + 1, 0b100); WriteRegister(Register::KEY_CONTROL_BASE + 2, 0b100); // Centre button. No AKS channel, since we handle it in software. WriteRegister(Register::KEY_CONTROL_BASE + 3, 0b0); // Touch guard. Set as a guard, in channel 1. WriteRegister(Register::KEY_CONTROL_BASE + 4, 0b10100); // It's normal to press the wheel for a long time. Disable auto recalibration // so that the user's finger isn't calibrated away. WriteRegister(Register::RECALIBRATION_DELAY, 0); WriteRegister(Register::CHARGE_TIME, 0x10); // Unused extra keys. All disabled. for (int i = 5; i < 12; i++) { WriteRegister(Register::KEY_CONTROL_BASE + i, 1); } } TouchWheel::~TouchWheel() {} void TouchWheel::WriteRegister(uint8_t reg, uint8_t val) { // Addresses <= 5 are not writeable. Make sure we don't try. assert(reg > 5); uint8_t cmd[] = {static_cast(reg), val}; esp_err_t res = i2c_master_transmit(i2c_, cmd, 2, 100); if (res != ESP_OK) { ESP_LOGW(kTag, "write failed: %s", esp_err_to_name(res)); } } uint8_t TouchWheel::ReadRegister(uint8_t reg) { uint8_t cmd[] = {static_cast(reg)}; uint8_t data[] = {0}; if (i2c_master_transmit_receive(i2c_, cmd, 1, data, 1, 100) != ESP_OK) { return 0; } return data[0]; } void TouchWheel::Update() { // Read data from device into member struct bool has_data = !gpio_get_level(kIntPin); if (!has_data) { return; } uint8_t status = ReadRegister(Register::DETECTION_STATUS); if (status & 0b10000000) { // Still calibrating. return; } if (status & 0b10) { // Slider detect. uint8_t pos = ReadRegister(Register::SLIDER_POSITION); data_.wheel_position = pos; } if (status & 0b1) { // Key detect. Note that the touchwheel keys also trigger this. uint8_t reg = ReadRegister(Register::KEY_STATUS_A); data_.is_button_touched = reg & 0b1000; data_.is_wheel_touched = reg & 0b111; } else { data_.is_button_touched = false; data_.is_wheel_touched = false; } } TouchWheelData TouchWheel::GetTouchWheelData() const { return data_; } auto TouchWheel::Recalibrate() -> void { WriteRegister(CALIBRATE, 1); } auto TouchWheel::LowPowerMode(bool en) -> void { WriteRegister(LOW_POWER, en ? 0 : 1); } } // namespace drivers