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.
This commit is contained in:
parent
39b0fa806c
commit
effa49ba54
@ -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.
|
||||
|
@ -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()
|
||||
|
@ -1,108 +1,103 @@
|
||||
#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);
|
||||
// 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<Gestures>(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<Gestures>(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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user