Skip to content

Commit

Permalink
Add CST716 touch support and fused mode support
Browse files Browse the repository at this point in the history
The P8 smartwatches use the CST716 or CST816S chips in various modes.

The device ID of these chips cannot be used for runtime detection,
because it does not give the hardware revision / firmware configuration.
  • Loading branch information
StarGate01 committed Nov 30, 2022
1 parent 6f1b502 commit 48123d9
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 114 deletions.
5 changes: 5 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -799,23 +799,28 @@ add_definitions(-DTARGET_DEVICE_NAME="${TARGET_DEVICE}")
if(TARGET_DEVICE STREQUAL "PINETIME")
add_definitions(-DDRIVER_PINMAP_PINETIME)
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
add_definitions(-DDRIVER_TOUCH_DYNAMIC)
elseif(TARGET_DEVICE STREQUAL "MOY-TFK5") # P8a
add_definitions(-DDRIVER_PINMAP_P8)
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
add_definitions(-DDRIVER_TOUCH_GESTURE)
elseif(TARGET_DEVICE STREQUAL "MOY-TIN5") # P8a variant 2
add_definitions(-DDRIVER_PINMAP_P8)
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
add_definitions(-DDRIVER_TOUCH_GESTURE)
elseif(TARGET_DEVICE STREQUAL "MOY-TON5") # P8b
add_definitions(-DDRIVER_PINMAP_P8)
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)
elseif(TARGET_DEVICE STREQUAL "MOY-UNK") # P8b mirrored
add_definitions(-DDRIVER_PINMAP_P8)
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_DISPLAY_MIRROR)
add_definitions(-DDRIVER_TOUCH_REPORT)
else()
message(FATAL_ERROR "Invalid TARGET_DEVICE")
endif()
Expand Down
151 changes: 64 additions & 87 deletions src/drivers/Cst816s.cpp
Original file line number Diff line number Diff line change
@@ -1,130 +1,107 @@
#include "drivers/Cst816s.h"
#include "drivers/PinMap.h"
#include <FreeRTOS.h>
#include <legacy/nrf_drv_gpiote.h>
#include <nrfx_log.h>
#include <task.h>
#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);

// 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();

/*
[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);

/*
[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);
// 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;
}

// 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.

// 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);

// 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() {
Cst816S::TouchInfos info;
uint8_t touchData[7];

auto ret = twiMaster.Read(twiAddress, 0, touchData, sizeof(touchData));
if (ret != TwiMaster::ErrorCodes::NoError) {
info.isValid = false;
return info;
// 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.
}

// 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<Gestures>(touchData[gestureIndex]);

// 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;
// Read gesture metadata and first touch point coordinate block
Cst816S::TouchInfos info;
uint8_t touchData[P1_Y_POS_L + 1];
auto ret = twiMaster.Read(twiAddress, 0x00, touchData, sizeof(touchData));
if (ret != TwiMaster::ErrorCodes::NoError)
return info;
}

info.x = x;
info.y = y;
info.touching = (nbTouchPoints > 0);
info.gesture = gesture;
info.isValid = true;
// 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<Gestures>(touchData[GESTURE_ID]);

// 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));

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");
}

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;
}
35 changes: 12 additions & 23 deletions src/drivers/Cst816s.h
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
#pragma once

#include "drivers/Cst816s_registers.h"
#include "drivers/TwiMaster.h"

namespace Pinetime {
namespace Drivers {
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;
Expand Down Expand Up @@ -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;

Expand All @@ -70,6 +57,8 @@ namespace Pinetime {
uint8_t chipId;
uint8_t vendorId;
uint8_t fwVersion;

bool firstEvent = true;
};

}
Expand Down
8 changes: 7 additions & 1 deletion src/systemtask/SystemTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,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;
Expand Down
38 changes: 35 additions & 3 deletions src/touchhandler/TouchHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ||
Expand All @@ -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);
Expand Down

0 comments on commit 48123d9

Please sign in to comment.