diff --git a/doc/buildAndProgram.md b/doc/buildAndProgram.md index 6dfbc5fe..48cd847c 100644 --- a/doc/buildAndProgram.md +++ b/doc/buildAndProgram.md @@ -39,7 +39,7 @@ CMake configures the project according to variables you specify the command line **GDB_CLIENT_BIN_PATH**|Path to arm-none-eabi-gdb executable. Used only if `USE_GDB_CLIENT` is 1.|`-DGDB_CLIENT_BIN_PATH=/home/jf/nrf52/gcc-arm-none-eabi-9-2019-q4-major/bin/arm-none-eabi-gdb` **GDB_CLIENT_TARGET_REMOTE**|Target remote connection string. Used only if `USE_GDB_CLIENT` is 1.|`-DGDB_CLIENT_TARGET_REMOTE=/dev/ttyACM0` **BUILD_DFU (\*\*)**|Build DFU files while building (needs [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil)).|`-DBUILD_DFU=1` -**TARGET_DEVICE**|Target device, used for the pin map and the low frequency clock source. Allowed: `PINETIME, P8A, P8B`|`-DTARGET_DEVICE=PINETIME` (Default) +**TARGET_DEVICE**|Target device, used for the pin map, the low frequency clock source, and the touch driver behavior. Allowed: `PINETIME, P8A, P8B`|`-DTARGET_DEVICE=PINETIME` (Default) ####(**) Note about **CMAKE_BUILD_TYPE**: By default, this variable is set to *Release*. It compiles the code with size and speed optimizations. We use this value for all the binaries we publish when we [release](https://github.com/InfiniTimeOrg/InfiniTime/releases) new versions of InfiniTime. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3bb222e1..7bf52d76 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -796,12 +796,15 @@ add_definitions(-D__HEAP_SIZE=4096) add_definitions(-DTARGET_DEVICE_${TARGET_DEVICE}) if(TARGET_DEVICE STREQUAL "PINETIME") add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL + add_definitions(-DDRIVER_TOUCH_DYNAMIC) elseif(TARGET_DEVICE STREQUAL "P8A") add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL + add_definitions(-DDRIVER_TOUCH_GESTURE) elseif(TARGET_DEVICE STREQUAL "P8B") add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500) add_definitions(-DCLOCK_CONFIG_LF_CAL_ENABLED=1) + add_definitions(-DDRIVER_TOUCH_REPORT) else() message(FATAL_ERROR "Invalid TARGET_DEVICE") endif() diff --git a/src/drivers/Cst816s.cpp b/src/drivers/Cst816s.cpp index cf10c895..4fba71d1 100644 --- a/src/drivers/Cst816s.cpp +++ b/src/drivers/Cst816s.cpp @@ -1,108 +1,103 @@ #include "drivers/Cst816s.h" +#include "drivers/PinMap.h" #include #include #include #include -#include "drivers/PinMap.h" using namespace Pinetime::Drivers; -/* References : +/* + * References : * This implementation is based on this article : * https://medium.com/@ly.lee/building-a-rust-driver-for-pinetimes-touch-controller-cbc1a5d5d3e9 Touch panel datasheet (weird chinese * translation) : https://wiki.pine64.org/images/5/51/CST816S%E6%95%B0%E6%8D%AE%E6%89%8B%E5%86%8CV1.1.en.pdf * - * TODO : we need a complete datasheet and protocol reference! + * TODO: We need a complete datasheet and protocol reference! + * For register desciptions, see Cst816s_registers.h. Information was collected from various chinese datasheets and documents. * */ Cst816S::Cst816S(TwiMaster& twiMaster, uint8_t twiAddress) : twiMaster {twiMaster}, twiAddress {twiAddress} { } bool Cst816S::Init() { + // Reset the touch driver nrf_gpio_cfg_output(PinMap::Cst816sReset); nrf_gpio_pin_clear(PinMap::Cst816sReset); - vTaskDelay(5); + vTaskDelay(10); nrf_gpio_pin_set(PinMap::Cst816sReset); vTaskDelay(50); - // Wake the touchpanel up - uint8_t dummy; - twiMaster.Read(twiAddress, 0x15, &dummy, 1); - vTaskDelay(5); - twiMaster.Read(twiAddress, 0xa7, &dummy, 1); - vTaskDelay(5); + // Chip ID is suspected to be dependent on embedded firmware and factory configuration, + // and not (just) on hardware type and revision. + if (twiMaster.Read(twiAddress, CHIP_ID, &chipId, 1) == TwiMaster::ErrorCodes::TransactionFailed) { + chipId = 0xFF; + } + // Vendor / project ID and firmware version can vary between devices. + if (twiMaster.Read(twiAddress, PROJ_ID, &vendorId, 1) == TwiMaster::ErrorCodes::TransactionFailed) { + vendorId = 0xFF; + } + if (twiMaster.Read(twiAddress, FW_VERSION, &fwVersion, 1) == TwiMaster::ErrorCodes::TransactionFailed) { + fwVersion = 0xFF; + } - // TODO This function check that the device IDs from the controller are equal to the ones - // we expect. However, it seems to return false positive (probably in case of communication issue). - // Also, it seems that some users have pinetimes that works correctly but that report different device IDs - // Until we know more about this, we'll just read the IDs but not take any action in case they are not 'valid' - CheckDeviceIds(); + // These configuration settings will be ignored by chips which were + // fused / pre-configured in the factory (GESTURE and REPORT settings). + // This mainly applies to CST716, however there may be CST816S with static configurations as well. + // The other, freely configureable ones (DYNAMIC), are configured in reporting mode here. - /* - [2] EnConLR - Continuous operation can slide around - [1] EnConUD - Slide up and down to enable continuous operation - [0] EnDClick - Enable Double-click action - */ - static constexpr uint8_t motionMask = 0b00000101; - twiMaster.Write(twiAddress, 0xEC, &motionMask, 1); + // Configure motion behaviour + static constexpr uint8_t motionMask = MOTION_MASK_EN_DCLICK | MOTION_MASK_EN_CON_UD | MOTION_MASK_EN_CON_LR; + twiMaster.Write(twiAddress, MOTION_MASK, &motionMask, 1); - /* - [7] EnTest - Interrupt pin to test, enable automatic periodic issued after a low pulse. - [6] EnTouch - When a touch is detected, a periodic pulsed Low. - [5] EnChange - Upon detecting a touch state changes, pulsed Low. - [4] EnMotion - When the detected gesture is pulsed Low. - [0] OnceWLP - Press gesture only issue a pulse signal is low. - */ - static constexpr uint8_t irqCtl = 0b01110000; - twiMaster.Write(twiAddress, 0xFA, &irqCtl, 1); + // Configure interrupt generating events + static constexpr uint8_t irqCtl = IRQ_CTL_EN_MOTION | IRQ_CTL_EN_CHANGE | IRQ_CTL_EN_TOUCH; + twiMaster.Write(twiAddress, IRQ_CTL, &irqCtl, 1); return true; } Cst816S::TouchInfos Cst816S::GetTouchInfo() { + // Some chips fail to wake up even though the reset pin has been toggled. + // They only provide an I2C communication window after a touch interrupt, + // so the first touch interrupt is used to force initialisation. + if (firstEvent) { + Init(); + firstEvent = false; + // The data registers should now be reset, so this touch event will be detected as invalid. + } + + // Read gesture metadata and first touch point coordinate block Cst816S::TouchInfos info; - uint8_t touchData[7]; - - auto ret = twiMaster.Read(twiAddress, 0, touchData, sizeof(touchData)); - if (ret != TwiMaster::ErrorCodes::NoError) { - info.isValid = false; + uint8_t touchData[P1_Y_POS_L + 1]; + auto ret = twiMaster.Read(twiAddress, 0x00, touchData, sizeof(touchData)); + if (ret != TwiMaster::ErrorCodes::NoError) return info; - } - // This can only be 0 or 1 - uint8_t nbTouchPoints = touchData[touchPointNumIndex] & 0x0f; - uint8_t xHigh = touchData[touchXHighIndex] & 0x0f; - uint8_t xLow = touchData[touchXLowIndex]; - uint16_t x = (xHigh << 8) | xLow; - uint8_t yHigh = touchData[touchYHighIndex] & 0x0f; - uint8_t yLow = touchData[touchYLowIndex]; - uint16_t y = (yHigh << 8) | yLow; - Gestures gesture = static_cast(touchData[gestureIndex]); + // Assemble 12 bit point coordinates from lower 8 bits and higher 4 bits + info.x = ((touchData[P1_X_POS_H] & POS_H_POS_MASK) << 8) | touchData[P1_X_POS_L]; + info.y = ((touchData[P1_Y_POS_H] & POS_H_POS_MASK) << 8) | touchData[P1_Y_POS_L]; + // Evaluate number of touch points + info.touching = (touchData[TD_STATUS] & TD_STATUS_MASK) > 0; + // Decode gesture ID + info.gesture = static_cast(touchData[GESTURE_ID]); - // Validity check - if (x >= maxX || y >= maxY || - (gesture != Gestures::None && gesture != Gestures::SlideDown && gesture != Gestures::SlideUp && gesture != Gestures::SlideLeft && - gesture != Gestures::SlideRight && gesture != Gestures::SingleTap && gesture != Gestures::DoubleTap && - gesture != Gestures::LongPress)) { - info.isValid = false; - return info; - } + // Validity check, verify value ranges + info.isValid = (info.x < maxX && info.y < maxY && + (info.gesture == Gestures::None || info.gesture == Gestures::SlideDown || info.gesture == Gestures::SlideUp || + info.gesture == Gestures::SlideLeft || info.gesture == Gestures::SlideRight || info.gesture == Gestures::SingleTap || + info.gesture == Gestures::DoubleTap || info.gesture == Gestures::LongPress)); - info.x = x; - info.y = y; - info.touching = (nbTouchPoints > 0); - info.gesture = gesture; - info.isValid = true; return info; } void Cst816S::Sleep() { - nrf_gpio_pin_clear(PinMap::Cst816sReset); - vTaskDelay(5); - nrf_gpio_pin_set(PinMap::Cst816sReset); - vTaskDelay(50); - static constexpr uint8_t sleepValue = 0x03; - twiMaster.Write(twiAddress, 0xA5, &sleepValue, 1); + // This only controls the CST716, the CST816S will ignore this register. + // The CST816S power state is managed using auto-sleep. + + static constexpr uint8_t sleepValue = PWR_MODE_DEEP_SLEEP; + twiMaster.Write(twiAddress, PWR_MODE_CST716, &sleepValue, 1); + NRF_LOG_INFO("[TOUCHPANEL] Sleep"); } @@ -110,21 +105,3 @@ void Cst816S::Wakeup() { Init(); NRF_LOG_INFO("[TOUCHPANEL] Wakeup"); } - -bool Cst816S::CheckDeviceIds() { - // There's mixed information about which register contains which information - if (twiMaster.Read(twiAddress, 0xA7, &chipId, 1) == TwiMaster::ErrorCodes::TransactionFailed) { - chipId = 0xFF; - return false; - } - if (twiMaster.Read(twiAddress, 0xA8, &vendorId, 1) == TwiMaster::ErrorCodes::TransactionFailed) { - vendorId = 0xFF; - return false; - } - if (twiMaster.Read(twiAddress, 0xA9, &fwVersion, 1) == TwiMaster::ErrorCodes::TransactionFailed) { - fwVersion = 0xFF; - return false; - } - - return chipId == 0xb4 && vendorId == 0 && fwVersion == 1; -} diff --git a/src/drivers/Cst816s.h b/src/drivers/Cst816s.h index 9d426c9d..fde7149f 100644 --- a/src/drivers/Cst816s.h +++ b/src/drivers/Cst816s.h @@ -1,5 +1,6 @@ #pragma once +#include "drivers/Cst816s_registers.h" #include "drivers/TwiMaster.h" namespace Pinetime { @@ -7,14 +8,15 @@ namespace Pinetime { class Cst816S { public: enum class Gestures : uint8_t { - None = 0x00, - SlideDown = 0x01, - SlideUp = 0x02, - SlideLeft = 0x03, - SlideRight = 0x04, - SingleTap = 0x05, - DoubleTap = 0x0B, - LongPress = 0x0C + None = GESTURE_ID_NONE, + SlideDown = GESTURE_ID_SLIDE_DOWN, + SlideUp = GESTURE_ID_SLIDE_UP, + SlideLeft = GESTURE_ID_SLIDE_LEFT, + SlideRight = GESTURE_ID_SLIDE_RIGHT, + SingleTap = GESTURE_ID_SINGLE_TAP, + DoubleTap = GESTURE_ID_DOUBLE_TAP, + LongPress = GESTURE_ID_LONG_PRESS, + Invalid = 0xFF }; struct TouchInfos { uint16_t x = 0; @@ -46,21 +48,6 @@ namespace Pinetime { } private: - bool CheckDeviceIds(); - - // Unused/Unavailable commented out - static constexpr uint8_t gestureIndex = 1; - static constexpr uint8_t touchPointNumIndex = 2; - // static constexpr uint8_t touchEventIndex = 3; - static constexpr uint8_t touchXHighIndex = 3; - static constexpr uint8_t touchXLowIndex = 4; - // static constexpr uint8_t touchIdIndex = 5; - static constexpr uint8_t touchYHighIndex = 5; - static constexpr uint8_t touchYLowIndex = 6; - // static constexpr uint8_t touchStep = 6; - // static constexpr uint8_t touchXYIndex = 7; - // static constexpr uint8_t touchMiscIndex = 8; - static constexpr uint8_t maxX = 240; static constexpr uint8_t maxY = 240; @@ -70,6 +57,8 @@ namespace Pinetime { uint8_t chipId; uint8_t vendorId; uint8_t fwVersion; + + bool firstEvent = true; }; } diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index 200cf3b0..09be50eb 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -379,7 +379,13 @@ void SystemTask::Work() { // Double Tap needs the touch screen to be in normal mode if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) { - touchPanel.Sleep(); +// REPORT and GESTURE mode sensors must be normal mode for single tap as well +#if !defined(DRIVER_TOUCH_DYNAMIC) + if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap)) +#endif + { + touchPanel.Sleep(); + } } state = SystemTaskState::Sleeping; diff --git a/src/touchhandler/TouchHandler.cpp b/src/touchhandler/TouchHandler.cpp index 0e4fb541..8fef817d 100644 --- a/src/touchhandler/TouchHandler.cpp +++ b/src/touchhandler/TouchHandler.cpp @@ -50,11 +50,36 @@ Pinetime::Applications::TouchEvents TouchHandler::GestureGet() { bool TouchHandler::GetNewTouchInfo() { info = touchPanel.GetTouchInfo(); - - if (!info.isValid) { + if (!info.isValid) return false; - } + // REPORT configurations (P8b variants) of the fused (usually) Cst716 + // generate multiple "none" gesture events with info.touching == true during the physical gesture. + // The last event is a e.g. "slide" event with info.touching == true. + // gestureReleased state does not have to be computed manually, instead it occurs when event != "none". + + // GESTURE configurations (P8a variants) of the fused (usually) Cst716 generate no events during the physical gesture. + // The only event is a e.g. "slide" event with info.touching == true. + // gestureReleased state does not have to be computed manually, instead it occurs everytime. + + // DYNAMIC configurations (PineTime) are configured in reporting mode during initialisation. + // Usually based on the Cst816s, they generate multiple e.g. "slide" gesture events with info.touching == true during the physical + // gesture. The last of these e.g. "slide" events has info.touching == false. gestureReleased state is computed manually by checking for + // the transition to info.touching == false. + + // Unfortunately, there is no way to reliably obtain which configuration is used at runtime. + // In all cases, the event is bubbled up once the gesture is released. + +#if defined(DRIVER_TOUCH_REPORT) + if (info.gesture != Pinetime::Drivers::Cst816S::Gestures::None) { + gesture = ConvertGesture(info.gesture); + info.touching = false; + } +#elif defined(DRIVER_TOUCH_GESTURE) + if (info.gesture != Pinetime::Drivers::Cst816S::Gestures::None) { + gesture = ConvertGesture(info.gesture); + } +#elif defined(DRIVER_TOUCH_DYNAMIC) if (info.gesture != Pinetime::Drivers::Cst816S::Gestures::None) { if (gestureReleased) { if (info.gesture == Pinetime::Drivers::Cst816S::Gestures::SlideDown || @@ -75,15 +100,22 @@ bool TouchHandler::GetNewTouchInfo() { if (!info.touching) { gestureReleased = true; } +#endif return true; } void TouchHandler::UpdateLvglTouchPoint() { if (info.touching) { +#if defined(DRIVER_TOUCH_GESTURE) + // GESTURE config only generates a single event / state change + // so the LVGL wrapper is used to generate a successive release state update + lvgl.SetNewTap(info.x, info.y); +#else if (!isCancelled) { lvgl.SetNewTouchPoint(info.x, info.y, true); } +#endif } else { if (isCancelled) { lvgl.SetNewTouchPoint(-1, -1, false);