Skip to content

Commit 48123d9

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 6f1b502 commit 48123d9

File tree

5 files changed

+123
-114
lines changed

5 files changed

+123
-114
lines changed

src/CMakeLists.txt

+5
Original file line numberDiff line numberDiff line change
@@ -799,23 +799,28 @@ add_definitions(-DTARGET_DEVICE_NAME="${TARGET_DEVICE}")
799799
if(TARGET_DEVICE STREQUAL "PINETIME")
800800
add_definitions(-DDRIVER_PINMAP_PINETIME)
801801
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
802+
add_definitions(-DDRIVER_TOUCH_DYNAMIC)
802803
elseif(TARGET_DEVICE STREQUAL "MOY-TFK5") # P8a
803804
add_definitions(-DDRIVER_PINMAP_P8)
804805
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
806+
add_definitions(-DDRIVER_TOUCH_GESTURE)
805807
elseif(TARGET_DEVICE STREQUAL "MOY-TIN5") # P8a variant 2
806808
add_definitions(-DDRIVER_PINMAP_P8)
807809
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
810+
add_definitions(-DDRIVER_TOUCH_GESTURE)
808811
elseif(TARGET_DEVICE STREQUAL "MOY-TON5") # P8b
809812
add_definitions(-DDRIVER_PINMAP_P8)
810813
add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
811814
add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
812815
add_definitions(-DCLOCK_CONFIG_LF_CAL_ENABLED=1)
816+
add_definitions(-DDRIVER_TOUCH_REPORT)
813817
elseif(TARGET_DEVICE STREQUAL "MOY-UNK") # P8b mirrored
814818
add_definitions(-DDRIVER_PINMAP_P8)
815819
add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
816820
add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
817821
add_definitions(-DCLOCK_CONFIG_LF_CAL_ENABLED=1)
818822
add_definitions(-DDRIVER_DISPLAY_MIRROR)
823+
add_definitions(-DDRIVER_TOUCH_REPORT)
819824
else()
820825
message(FATAL_ERROR "Invalid TARGET_DEVICE")
821826
endif()

src/drivers/Cst816s.cpp

+64-87
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

+12-23
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
struct TouchInfos {
2022
uint16_t x = 0;
@@ -46,21 +48,6 @@ namespace Pinetime {
4648
}
4749

4850
private:
49-
bool CheckDeviceIds();
50-
51-
// Unused/Unavailable commented out
52-
static constexpr uint8_t gestureIndex = 1;
53-
static constexpr uint8_t touchPointNumIndex = 2;
54-
// static constexpr uint8_t touchEventIndex = 3;
55-
static constexpr uint8_t touchXHighIndex = 3;
56-
static constexpr uint8_t touchXLowIndex = 4;
57-
// static constexpr uint8_t touchIdIndex = 5;
58-
static constexpr uint8_t touchYHighIndex = 5;
59-
static constexpr uint8_t touchYLowIndex = 6;
60-
// static constexpr uint8_t touchStep = 6;
61-
// static constexpr uint8_t touchXYIndex = 7;
62-
// static constexpr uint8_t touchMiscIndex = 8;
63-
6451
static constexpr uint8_t maxX = 240;
6552
static constexpr uint8_t maxY = 240;
6653

@@ -70,6 +57,8 @@ namespace Pinetime {
7057
uint8_t chipId;
7158
uint8_t vendorId;
7259
uint8_t fwVersion;
60+
61+
bool firstEvent = true;
7362
};
7463

7564
}

src/systemtask/SystemTask.cpp

+7-1
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,13 @@ void SystemTask::Work() {
377377

378378
// Double Tap needs the touch screen to be in normal mode
379379
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
380-
touchPanel.Sleep();
380+
// REPORT and GESTURE mode sensors must be normal mode for single tap as well
381+
#if !defined(DRIVER_TOUCH_DYNAMIC)
382+
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap))
383+
#endif
384+
{
385+
touchPanel.Sleep();
386+
}
381387
}
382388

383389
state = SystemTaskState::Sleeping;

src/touchhandler/TouchHandler.cpp

+35-3
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,36 @@ Pinetime::Applications::TouchEvents TouchHandler::GestureGet() {
5050

5151
bool TouchHandler::GetNewTouchInfo() {
5252
info = touchPanel.GetTouchInfo();
53-
54-
if (!info.isValid) {
53+
if (!info.isValid)
5554
return false;
56-
}
5755

56+
// REPORT configurations (P8b variants) of the fused (usually) Cst716
57+
// generate multiple "none" gesture events with info.touching == true during the physical gesture.
58+
// The last event is a e.g. "slide" event with info.touching == true.
59+
// gestureReleased state does not have to be computed manually, instead it occurs when event != "none".
60+
61+
// GESTURE configurations (P8a variants) of the fused (usually) Cst716 generate no events during the physical gesture.
62+
// The only event is a e.g. "slide" event with info.touching == true.
63+
// gestureReleased state does not have to be computed manually, instead it occurs everytime.
64+
65+
// DYNAMIC configurations (PineTime) are configured in reporting mode during initialisation.
66+
// Usually based on the Cst816s, they generate multiple e.g. "slide" gesture events with info.touching == true during the physical
67+
// gesture. The last of these e.g. "slide" events has info.touching == false. gestureReleased state is computed manually by checking for
68+
// the transition to info.touching == false.
69+
70+
// Unfortunately, there is no way to reliably obtain which configuration is used at runtime.
71+
// In all cases, the event is bubbled up once the gesture is released.
72+
73+
#if defined(DRIVER_TOUCH_REPORT)
74+
if (info.gesture != Pinetime::Drivers::Cst816S::Gestures::None) {
75+
gesture = ConvertGesture(info.gesture);
76+
info.touching = false;
77+
}
78+
#elif defined(DRIVER_TOUCH_GESTURE)
79+
if (info.gesture != Pinetime::Drivers::Cst816S::Gestures::None) {
80+
gesture = ConvertGesture(info.gesture);
81+
}
82+
#elif defined(DRIVER_TOUCH_DYNAMIC)
5883
if (info.gesture != Pinetime::Drivers::Cst816S::Gestures::None) {
5984
if (gestureReleased) {
6085
if (info.gesture == Pinetime::Drivers::Cst816S::Gestures::SlideDown ||
@@ -75,15 +100,22 @@ bool TouchHandler::GetNewTouchInfo() {
75100
if (!info.touching) {
76101
gestureReleased = true;
77102
}
103+
#endif
78104

79105
return true;
80106
}
81107

82108
void TouchHandler::UpdateLvglTouchPoint() {
83109
if (info.touching) {
110+
#if defined(DRIVER_TOUCH_GESTURE)
111+
// GESTURE config only generates a single event / state change
112+
// so the LVGL wrapper is used to generate a successive release state update
113+
lvgl.SetNewTap(info.x, info.y);
114+
#else
84115
if (!isCancelled) {
85116
lvgl.SetNewTouchPoint(info.x, info.y, true);
86117
}
118+
#endif
87119
} else {
88120
if (isCancelled) {
89121
lvgl.SetNewTouchPoint(-1, -1, false);

0 commit comments

Comments
 (0)