Skip to content

Commit ccbf78c

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 8d7b20b commit ccbf78c

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
@@ -789,22 +789,27 @@ add_definitions(-DTARGET_DEVICE_${TARGET_DEVICE})
789789
if(TARGET_DEVICE STREQUAL "PINETIME")
790790
add_definitions(-DDRIVER_PINMAP_PINETIME)
791791
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
792+
add_definitions(-DDRIVER_TOUCH_DYNAMIC)
792793
elseif(TARGET_DEVICE STREQUAL "MOY-TFK5") # P8a
793794
add_definitions(-DDRIVER_PINMAP_P8)
794795
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
796+
add_definitions(-DDRIVER_TOUCH_GESTURE)
795797
elseif(TARGET_DEVICE STREQUAL "MOY-TIN5") # P8a variant 2
796798
add_definitions(-DDRIVER_PINMAP_P8)
797799
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
800+
add_definitions(-DDRIVER_TOUCH_GESTURE)
798801
elseif(TARGET_DEVICE STREQUAL "MOY-TON5") # P8b
799802
add_definitions(-DDRIVER_PINMAP_P8)
800803
add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
801804
add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
802805
add_definitions(-DCLOCK_CONFIG_LF_CAL_ENABLED=1)
806+
add_definitions(-DDRIVER_TOUCH_REPORT)
803807
elseif(TARGET_DEVICE STREQUAL "MOY-UNK") # P8b mirrored
804808
add_definitions(-DDRIVER_PINMAP_P8)
805809
add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
806810
add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
807811
add_definitions(-DCLOCK_CONFIG_LF_CAL_ENABLED=1)
812+
add_definitions(-DDRIVER_TOUCH_REPORT)
808813
else()
809814
message(FATAL_ERROR "Invalid TARGET_DEVICE")
810815
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
@@ -376,7 +376,13 @@ void SystemTask::Work() {
376376

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

382388
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)