Skip to content

Commit 167950a

Browse files
committed
Add CST716 touch support and fused mode support
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.
1 parent b294599 commit 167950a

File tree

5 files changed

+137
-112
lines changed

5 files changed

+137
-112
lines changed

src/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,23 +795,28 @@ add_definitions(-DTARGET_DEVICE_NAME="${TARGET_DEVICE}")
795795
if(TARGET_DEVICE STREQUAL "PINETIME")
796796
add_definitions(-DDRIVER_PINMAP_PINETIME)
797797
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
798+
add_definitions(-DDRIVER_TOUCH_DYNAMIC)
798799
elseif(TARGET_DEVICE STREQUAL "MOY_TFK5") # P8a
799800
add_definitions(-DDRIVER_PINMAP_P8)
800801
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
802+
add_definitions(-DDRIVER_TOUCH_GESTURE)
801803
elseif(TARGET_DEVICE STREQUAL "MOY_TIN5") # P8a variant 2
802804
add_definitions(-DDRIVER_PINMAP_P8)
803805
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
806+
add_definitions(-DDRIVER_TOUCH_GESTURE)
804807
elseif(TARGET_DEVICE STREQUAL "MOY_TON5") # P8b
805808
add_definitions(-DDRIVER_PINMAP_P8)
806809
add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
807810
add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
808811
add_definitions(-DCLOCK_CONFIG_LF_CAL_ENABLED=1)
812+
add_definitions(-DDRIVER_TOUCH_REPORT)
809813
elseif(TARGET_DEVICE STREQUAL "MOY_UNK") # P8b mirrored
810814
add_definitions(-DDRIVER_PINMAP_P8)
811815
add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
812816
add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
813817
add_definitions(-DCLOCK_CONFIG_LF_CAL_ENABLED=1)
814818
add_definitions(-DDRIVER_DISPLAY_MIRROR)
819+
add_definitions(-DDRIVER_TOUCH_REPORT)
815820
else()
816821
message(FATAL_ERROR "Invalid TARGET_DEVICE")
817822
endif()

src/drivers/Cst816s.cpp

Lines changed: 64 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,130 +1,107 @@
11
#include "drivers/Cst816s.h"
2+
#include "drivers/PinMap.h"
23
#include <FreeRTOS.h>
34
#include <legacy/nrf_drv_gpiote.h>
45
#include <nrfx_log.h>
56
#include <task.h>
6-
#include "drivers/PinMap.h"
77

88
using namespace Pinetime::Drivers;
99

10-
/* References :
10+
/*
11+
* References :
1112
* This implementation is based on this article :
1213
* https://medium.com/@ly.lee/building-a-rust-driver-for-pinetimes-touch-controller-cbc1a5d5d3e9 Touch panel datasheet (weird chinese
1314
* translation) : https://wiki.pine64.org/images/5/51/CST816S%E6%95%B0%E6%8D%AE%E6%89%8B%E5%86%8CV1.1.en.pdf
1415
*
15-
* TODO : we need a complete datasheet and protocol reference!
16+
* TODO: We need a complete datasheet and protocol reference!
17+
* For register desciptions, see Cst816s_registers.h. Information was collected from various chinese datasheets and documents.
1618
* */
1719

1820
Cst816S::Cst816S(TwiMaster& twiMaster, uint8_t twiAddress) : twiMaster {twiMaster}, twiAddress {twiAddress} {
1921
}
2022

2123
bool Cst816S::Init() {
24+
// Reset the touch driver
2225
nrf_gpio_cfg_output(PinMap::Cst816sReset);
2326
nrf_gpio_pin_clear(PinMap::Cst816sReset);
24-
vTaskDelay(5);
27+
vTaskDelay(10);
2528
nrf_gpio_pin_set(PinMap::Cst816sReset);
2629
vTaskDelay(50);
2730

28-
// Wake the touchpanel up
29-
uint8_t dummy;
30-
twiMaster.Read(twiAddress, 0x15, &dummy, 1);
31-
vTaskDelay(5);
32-
twiMaster.Read(twiAddress, 0xa7, &dummy, 1);
33-
vTaskDelay(5);
34-
35-
// TODO This function check that the device IDs from the controller are equal to the ones
36-
// we expect. However, it seems to return false positive (probably in case of communication issue).
37-
// Also, it seems that some users have pinetimes that works correctly but that report different device IDs
38-
// Until we know more about this, we'll just read the IDs but not take any action in case they are not 'valid'
39-
CheckDeviceIds();
40-
41-
/*
42-
[2] EnConLR - Continuous operation can slide around
43-
[1] EnConUD - Slide up and down to enable continuous operation
44-
[0] EnDClick - Enable Double-click action
45-
*/
46-
static constexpr uint8_t motionMask = 0b00000101;
47-
twiMaster.Write(twiAddress, 0xEC, &motionMask, 1);
48-
49-
/*
50-
[7] EnTest - Interrupt pin to test, enable automatic periodic issued after a low pulse.
51-
[6] EnTouch - When a touch is detected, a periodic pulsed Low.
52-
[5] EnChange - Upon detecting a touch state changes, pulsed Low.
53-
[4] EnMotion - When the detected gesture is pulsed Low.
54-
[0] OnceWLP - Press gesture only issue a pulse signal is low.
55-
*/
56-
static constexpr uint8_t irqCtl = 0b01110000;
57-
twiMaster.Write(twiAddress, 0xFA, &irqCtl, 1);
31+
// Chip ID is suspected to be dependent on embedded firmware and factory configuration,
32+
// and not (just) on hardware type and revision.
33+
if (twiMaster.Read(twiAddress, CHIP_ID, &chipId, 1) == TwiMaster::ErrorCodes::TransactionFailed) {
34+
chipId = 0xFF;
35+
}
36+
// Vendor / project ID and firmware version can vary between devices.
37+
if (twiMaster.Read(twiAddress, PROJ_ID, &vendorId, 1) == TwiMaster::ErrorCodes::TransactionFailed) {
38+
vendorId = 0xFF;
39+
}
40+
if (twiMaster.Read(twiAddress, FW_VERSION, &fwVersion, 1) == TwiMaster::ErrorCodes::TransactionFailed) {
41+
fwVersion = 0xFF;
42+
}
43+
44+
// These configuration settings will be ignored by chips which were
45+
// fused / pre-configured in the factory (GESTURE and REPORT settings).
46+
// This mainly applies to CST716, however there may be CST816S with static configurations as well.
47+
// The other, freely configureable ones (DYNAMIC), are configured in reporting mode here.
48+
49+
// Configure motion behaviour
50+
static constexpr uint8_t motionMask = MOTION_MASK_EN_DCLICK | MOTION_MASK_EN_CON_UD | MOTION_MASK_EN_CON_LR;
51+
twiMaster.Write(twiAddress, MOTION_MASK, &motionMask, 1);
52+
53+
// Configure interrupt generating events
54+
static constexpr uint8_t irqCtl = IRQ_CTL_EN_MOTION | IRQ_CTL_EN_CHANGE | IRQ_CTL_EN_TOUCH;
55+
twiMaster.Write(twiAddress, IRQ_CTL, &irqCtl, 1);
5856

5957
return true;
6058
}
6159

6260
Cst816S::TouchInfos Cst816S::GetTouchInfo() {
63-
Cst816S::TouchInfos info;
64-
uint8_t touchData[7];
65-
66-
auto ret = twiMaster.Read(twiAddress, 0, touchData, sizeof(touchData));
67-
if (ret != TwiMaster::ErrorCodes::NoError) {
68-
info.isValid = false;
69-
return info;
61+
// Some chips fail to wake up even though the reset pin has been toggled.
62+
// They only provide an I2C communication window after a touch interrupt,
63+
// so the first touch interrupt is used to force initialisation.
64+
if (firstEvent) {
65+
Init();
66+
firstEvent = false;
67+
// The data registers should now be reset, so this touch event will be detected as invalid.
7068
}
7169

72-
// This can only be 0 or 1
73-
uint8_t nbTouchPoints = touchData[touchPointNumIndex] & 0x0f;
74-
uint8_t xHigh = touchData[touchXHighIndex] & 0x0f;
75-
uint8_t xLow = touchData[touchXLowIndex];
76-
uint16_t x = (xHigh << 8) | xLow;
77-
uint8_t yHigh = touchData[touchYHighIndex] & 0x0f;
78-
uint8_t yLow = touchData[touchYLowIndex];
79-
uint16_t y = (yHigh << 8) | yLow;
80-
Gestures gesture = static_cast<Gestures>(touchData[gestureIndex]);
81-
82-
// Validity check
83-
if (x >= maxX || y >= maxY ||
84-
(gesture != Gestures::None && gesture != Gestures::SlideDown && gesture != Gestures::SlideUp && gesture != Gestures::SlideLeft &&
85-
gesture != Gestures::SlideRight && gesture != Gestures::SingleTap && gesture != Gestures::DoubleTap &&
86-
gesture != Gestures::LongPress)) {
87-
info.isValid = false;
70+
// Read gesture metadata and first touch point coordinate block
71+
Cst816S::TouchInfos info;
72+
uint8_t touchData[P1_Y_POS_L + 1];
73+
auto ret = twiMaster.Read(twiAddress, 0x00, touchData, sizeof(touchData));
74+
if (ret != TwiMaster::ErrorCodes::NoError)
8875
return info;
89-
}
9076

91-
info.x = x;
92-
info.y = y;
93-
info.touching = (nbTouchPoints > 0);
94-
info.gesture = gesture;
95-
info.isValid = true;
77+
// Assemble 12 bit point coordinates from lower 8 bits and higher 4 bits
78+
info.x = ((touchData[P1_X_POS_H] & POS_H_POS_MASK) << 8) | touchData[P1_X_POS_L];
79+
info.y = ((touchData[P1_Y_POS_H] & POS_H_POS_MASK) << 8) | touchData[P1_Y_POS_L];
80+
// Evaluate number of touch points
81+
info.touching = (touchData[TD_STATUS] & TD_STATUS_MASK) > 0;
82+
// Decode gesture ID
83+
info.gesture = static_cast<Gestures>(touchData[GESTURE_ID]);
84+
85+
// Validity check, verify value ranges
86+
info.isValid = (info.x < maxX && info.y < maxY &&
87+
(info.gesture == Gestures::None || info.gesture == Gestures::SlideDown || info.gesture == Gestures::SlideUp ||
88+
info.gesture == Gestures::SlideLeft || info.gesture == Gestures::SlideRight || info.gesture == Gestures::SingleTap ||
89+
info.gesture == Gestures::DoubleTap || info.gesture == Gestures::LongPress));
90+
9691
return info;
9792
}
9893

9994
void Cst816S::Sleep() {
100-
nrf_gpio_pin_clear(PinMap::Cst816sReset);
101-
vTaskDelay(5);
102-
nrf_gpio_pin_set(PinMap::Cst816sReset);
103-
vTaskDelay(50);
104-
static constexpr uint8_t sleepValue = 0x03;
105-
twiMaster.Write(twiAddress, 0xA5, &sleepValue, 1);
95+
// This only controls the CST716, the CST816S will ignore this register.
96+
// The CST816S power state is managed using auto-sleep.
97+
98+
static constexpr uint8_t sleepValue = PWR_MODE_DEEP_SLEEP;
99+
twiMaster.Write(twiAddress, PWR_MODE_CST716, &sleepValue, 1);
100+
106101
NRF_LOG_INFO("[TOUCHPANEL] Sleep");
107102
}
108103

109104
void Cst816S::Wakeup() {
110105
Init();
111106
NRF_LOG_INFO("[TOUCHPANEL] Wakeup");
112107
}
113-
114-
bool Cst816S::CheckDeviceIds() {
115-
// There's mixed information about which register contains which information
116-
if (twiMaster.Read(twiAddress, 0xA7, &chipId, 1) == TwiMaster::ErrorCodes::TransactionFailed) {
117-
chipId = 0xFF;
118-
return false;
119-
}
120-
if (twiMaster.Read(twiAddress, 0xA8, &vendorId, 1) == TwiMaster::ErrorCodes::TransactionFailed) {
121-
vendorId = 0xFF;
122-
return false;
123-
}
124-
if (twiMaster.Read(twiAddress, 0xA9, &fwVersion, 1) == TwiMaster::ErrorCodes::TransactionFailed) {
125-
fwVersion = 0xFF;
126-
return false;
127-
}
128-
129-
return chipId == 0xb4 && vendorId == 0 && fwVersion == 1;
130-
}

src/drivers/Cst816s.h

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
#pragma once
22

3+
#include "drivers/Cst816s_registers.h"
34
#include "drivers/TwiMaster.h"
45

56
namespace Pinetime {
67
namespace Drivers {
78
class Cst816S {
89
public:
910
enum class Gestures : uint8_t {
10-
None = 0x00,
11-
SlideDown = 0x01,
12-
SlideUp = 0x02,
13-
SlideLeft = 0x03,
14-
SlideRight = 0x04,
15-
SingleTap = 0x05,
16-
DoubleTap = 0x0B,
17-
LongPress = 0x0C
11+
None = GESTURE_ID_NONE,
12+
SlideDown = GESTURE_ID_SLIDE_DOWN,
13+
SlideUp = GESTURE_ID_SLIDE_UP,
14+
SlideLeft = GESTURE_ID_SLIDE_LEFT,
15+
SlideRight = GESTURE_ID_SLIDE_RIGHT,
16+
SingleTap = GESTURE_ID_SINGLE_TAP,
17+
DoubleTap = GESTURE_ID_DOUBLE_TAP,
18+
LongPress = GESTURE_ID_LONG_PRESS,
19+
Invalid = 0xFF
1820
};
1921

2022
struct TouchInfos {
@@ -49,21 +51,6 @@ namespace Pinetime {
4951
}
5052

5153
private:
52-
bool CheckDeviceIds();
53-
54-
// Unused/Unavailable commented out
55-
static constexpr uint8_t gestureIndex = 1;
56-
static constexpr uint8_t touchPointNumIndex = 2;
57-
// static constexpr uint8_t touchEventIndex = 3;
58-
static constexpr uint8_t touchXHighIndex = 3;
59-
static constexpr uint8_t touchXLowIndex = 4;
60-
// static constexpr uint8_t touchIdIndex = 5;
61-
static constexpr uint8_t touchYHighIndex = 5;
62-
static constexpr uint8_t touchYLowIndex = 6;
63-
// static constexpr uint8_t touchStep = 6;
64-
// static constexpr uint8_t touchXYIndex = 7;
65-
// static constexpr uint8_t touchMiscIndex = 8;
66-
6754
static constexpr uint8_t maxX = 240;
6855
static constexpr uint8_t maxY = 240;
6956

@@ -73,6 +60,8 @@ namespace Pinetime {
7360
uint8_t chipId;
7461
uint8_t vendorId;
7562
uint8_t fwVersion;
63+
64+
bool firstEvent = true;
7665
};
7766

7867
}

src/systemtask/SystemTask.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,13 @@ void SystemTask::Work() {
327327

328328
// Double Tap needs the touch screen to be in normal mode
329329
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
330-
touchPanel.Sleep();
330+
// REPORT and GESTURE mode sensors must be normal mode for single tap as well
331+
#if !defined(DRIVER_TOUCH_DYNAMIC)
332+
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap))
333+
#endif
334+
{
335+
touchPanel.Sleep();
336+
}
331337
}
332338

333339
state = SystemTaskState::Sleeping;

src/touchhandler/TouchHandler.cpp

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,34 @@ bool TouchHandler::ProcessTouchInfo(Drivers::Cst816S::TouchInfos info) {
3737
if (!info.isValid) {
3838
return false;
3939
}
40+
41+
// REPORT configurations (P8b variants) of the fused (usually) Cst716
42+
// generate multiple "none" gesture events with info.touching == true during the physical gesture.
43+
// The last event is a e.g. "slide" event with info.touching == true.
44+
// gestureReleased state does not have to be computed manually, instead it occurs when event != "none".
4045

41-
// Only a single gesture per touch
46+
// GESTURE configurations (P8a variants) of the fused (usually) Cst716 generate no events during the physical gesture.
47+
// The only event is a e.g. "slide" event with info.touching == true.
48+
// gestureReleased state does not have to be computed manually, instead it occurs everytime.
49+
50+
// DYNAMIC configurations (PineTime) are configured in reporting mode during initialisation.
51+
// Usually based on the Cst816s, they generate multiple e.g. "slide" gesture events with info.touching == true during the physical
52+
// gesture. The last of these e.g. "slide" events has info.touching == false. gestureReleased state is computed manually by checking for
53+
// the transition to info.touching == false.
54+
55+
// Unfortunately, there is no way to reliably obtain which configuration is used at runtime.
56+
// In all cases, the event is bubbled up once the gesture is released.
57+
58+
#if defined(DRIVER_TOUCH_REPORT)
59+
if (info.gesture != Pinetime::Drivers::Cst816S::Gestures::None) {
60+
gesture = ConvertGesture(info.gesture);
61+
info.touching = false;
62+
}
63+
#elif defined(DRIVER_TOUCH_GESTURE)
64+
if (info.gesture != Pinetime::Drivers::Cst816S::Gestures::None) {
65+
gesture = ConvertGesture(info.gesture);
66+
}
67+
#elif defined(DRIVER_TOUCH_DYNAMIC)
4268
if (info.gesture != Pinetime::Drivers::Cst816S::Gestures::None) {
4369
if (gestureReleased) {
4470
if (info.gesture == Pinetime::Drivers::Cst816S::Gestures::SlideDown ||
@@ -59,8 +85,30 @@ bool TouchHandler::ProcessTouchInfo(Drivers::Cst816S::TouchInfos info) {
5985
if (!info.touching) {
6086
gestureReleased = true;
6187
}
88+
#endif
6289

6390
currentTouchPoint = {info.x, info.y, info.touching};
6491

6592
return true;
6693
}
94+
95+
void TouchHandler::UpdateLvglTouchPoint() {
96+
if (info.touching) {
97+
#if defined(DRIVER_TOUCH_GESTURE)
98+
// GESTURE config only generates a single event / state change
99+
// so the LVGL wrapper is used to generate a successive release state update
100+
lvgl.SetNewTap(info.x, info.y);
101+
#else
102+
if (!isCancelled) {
103+
lvgl.SetNewTouchPoint(info.x, info.y, true);
104+
}
105+
#endif
106+
} else {
107+
if (isCancelled) {
108+
lvgl.SetNewTouchPoint(-1, -1, false);
109+
isCancelled = false;
110+
} else {
111+
lvgl.SetNewTouchPoint(info.x, info.y, false);
112+
}
113+
}
114+
}

0 commit comments

Comments
 (0)