Re-implement Core2 and other improvements (#141)

- Create real drivers instead of wrapping M5Unified/M5GFX
- Display HAL improvements (better default base class behaviour)
- Fixed bug with LVGL statusbar service locking (would hang indefinitely waiting for mutex, causing WDT issues)
- Fixes for `Critical.h`
- Fixes and improvements for `Dispatcher` and `DispatcherThread`
This commit is contained in:
Ken Van Hoeylandt 2024-12-30 14:17:47 +01:00 committed by GitHub
parent 50bd6e8bf6
commit 9581978fc7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 1223 additions and 77 deletions

View File

@ -117,12 +117,12 @@ bool TdeckDisplay::start() {
} }
if (esp_lcd_panel_swap_xy(panelHandle, true) != ESP_OK) { if (esp_lcd_panel_swap_xy(panelHandle, true) != ESP_OK) {
TT_LOG_E(TAG, "Failed to init panel"); TT_LOG_E(TAG, "Failed to swap XY ");
return false; return false;
} }
if (esp_lcd_panel_mirror(panelHandle, true, false) != ESP_OK) { if (esp_lcd_panel_mirror(panelHandle, true, false) != ESP_OK) {
TT_LOG_E(TAG, "Failed to init panel"); TT_LOG_E(TAG, "Failed to mirror panel");
return false; return false;
} }

View File

@ -1,5 +1,5 @@
idf_component_register( idf_component_register(
SRC_DIRS "Source" SRC_DIRS "Source" "Source/hal" "Source/ft6x36"
INCLUDE_DIRS "Source" INCLUDE_DIRS "Source"
REQUIRES Tactility M5stackShared M5Unified REQUIRES Tactility esp_lvgl_port esp_lcd esp_lcd_ili9341 driver vfs fatfs M5Unified
) )

View File

@ -0,0 +1,68 @@
#include <driver/i2c.h>
#include <driver/spi_master.h>
#include <intr_types.h>
#include "Log.h"
#include "hal/Core2DisplayConstants.h"
#include "M5Unified/src/utility/AXP192_Class.hpp"
#define TAG "core2"
#define CORE2_SPI2_PIN_SCLK GPIO_NUM_18
#define CORE2_SPI2_PIN_MOSI GPIO_NUM_23
#define CORE2_SPI2_PIN_MISO GPIO_NUM_38
m5::I2C_Class i2c;
m5::AXP192_Class axpDevice(0x34, 400000, &i2c);
static bool initSpi2() {
TT_LOG_I(TAG, LOG_MESSAGE_SPI_INIT_START_FMT, SPI2_HOST);
const spi_bus_config_t bus_config = {
.mosi_io_num = CORE2_SPI2_PIN_MOSI,
.miso_io_num = CORE2_SPI2_PIN_MISO,
.sclk_io_num = CORE2_SPI2_PIN_SCLK,
.data2_io_num = GPIO_NUM_NC,
.data3_io_num = GPIO_NUM_NC,
.data4_io_num = GPIO_NUM_NC,
.data5_io_num = GPIO_NUM_NC,
.data6_io_num = GPIO_NUM_NC,
.data7_io_num = GPIO_NUM_NC,
.max_transfer_sz = CORE2_LCD_DRAW_BUFFER_SIZE,
.flags = 0,
.isr_cpu_id = INTR_CPU_ID_AUTO,
.intr_flags = 0
};
if (spi_bus_initialize(SPI2_HOST, &bus_config, SPI_DMA_CH_AUTO) != ESP_OK) {
TT_LOG_E(TAG, LOG_MESSAGE_SPI_INIT_FAILED_FMT, SPI2_HOST);
return false;
}
return true;
}
bool initAxp() {
if (!i2c.begin(I2C_NUM_0, GPIO_NUM_21, GPIO_NUM_22)) {
TT_LOG_E(TAG, "I2C init failed");
return false;
}
if (!axpDevice.begin()) {
TT_LOG_E(TAG, "AXP init failed");
return false;
}
axpDevice.setLDO2(3300); // LCD + SD peripheral power supply
axpDevice.setLDO3(0); // VIB_MOTOR STOP
axpDevice.setGPIO2(false); // SPEAKER STOP
axpDevice.writeRegister8(0x9A, 255); // PWM 255 (LED OFF)
axpDevice.writeRegister8(0x92, 0x02); // GPIO1 PWM
axpDevice.setChargeCurrent(390); // Core2 battery = 390mAh
axpDevice.setDCDC3(3300);
return true;
}
bool initBoot() {
TT_LOG_I(TAG, "initBoot");
return initAxp() && initSpi2();
}

View File

@ -0,0 +1,3 @@
#pragma once
bool initBoot();

View File

@ -0,0 +1,31 @@
#include "Log.h"
#include "Thread.h"
#include "lvgl/LvglSync.h"
#include "esp_lvgl_port.h"
#define TAG "core2"
// LVGL
// The minimum task stack seems to be about 3500, but that crashes the wifi app in some scenarios
// At 4000, it crashes when the fps renderer is available
#define CORE2_LVGL_TASK_STACK_DEPTH 8192
bool initLvgl() {
const lvgl_port_cfg_t lvgl_cfg = {
.task_priority = tt::THREAD_PRIORITY_RENDER,
.task_stack = CORE2_LVGL_TASK_STACK_DEPTH,
.task_affinity = -1, // core pinning
.task_max_sleep_ms = 500,
.timer_period_ms = 5
};
TT_LOG_D(TAG, "LVGL port init");
if (lvgl_port_init(&lvgl_cfg) != ESP_OK) {
TT_LOG_E(TAG, "LVGL port init failed");
return false;
}
tt::lvgl::syncSet(&lvgl_port_lock, &lvgl_port_unlock);
return true;
}

View File

@ -0,0 +1,3 @@
#pragma once
bool initLvgl();

View File

@ -1,18 +1,22 @@
#include "M5stackCore2.h" #include "M5stackCore2.h"
#include "M5stackShared.h" #include "InitBoot.h"
#include "InitLvgl.h"
#include "hal/Core2Display.h"
#include "hal/Core2SdCard.h"
#include "hal/Core2Power.h"
extern const tt::hal::Configuration m5stack_core2 = { extern const tt::hal::Configuration m5stack_core2 = {
.initBoot = m5stack_bootstrap, .initBoot = initBoot,
.initLvgl = m5stack_lvgl_init, .initLvgl = initLvgl,
.createDisplay = createDisplay, .createDisplay = createDisplay,
.sdcard = createM5SdCard(), .sdcard = createSdCard(),
.power = m5stack_get_power, .power = createPower,
.i2c = { .i2c = {
tt::hal::i2c::Configuration { tt::hal::i2c::Configuration {
.name = "Internal", .name = "Internal",
.port = I2C_NUM_0, .port = I2C_NUM_0,
.initMode = tt::hal::i2c::InitByExternal, .initMode = tt::hal::i2c::InitByTactility,
.canReinit = false, .canReinit = false, // Might be set to try after trying out what it does AXP and screen
.hasMutableConfiguration = false, .hasMutableConfiguration = false,
.config = (i2c_config_t) { .config = (i2c_config_t) {
.mode = I2C_MODE_MASTER, .mode = I2C_MODE_MASTER,
@ -29,7 +33,7 @@ extern const tt::hal::Configuration m5stack_core2 = {
tt::hal::i2c::Configuration { tt::hal::i2c::Configuration {
.name = "External", // (Grove) .name = "External", // (Grove)
.port = I2C_NUM_1, .port = I2C_NUM_1,
.initMode = tt::hal::i2c::InitByExternal, .initMode = tt::hal::i2c::InitByTactility,
.canReinit = true, .canReinit = true,
.hasMutableConfiguration = true, .hasMutableConfiguration = true,
.config = (i2c_config_t) { .config = (i2c_config_t) {

View File

@ -0,0 +1,378 @@
#include "FT6X36.h"
#include "freertos/FreeRTOS.h"
#define CONFIG_FT6X36_DEBUG false
FT6X36 *FT6X36::_instance = nullptr;
static const char *TAG = "i2c-touch";
//Handle indicating I2C is ready to read the touch
SemaphoreHandle_t TouchSemaphore = xSemaphoreCreateBinary();
FT6X36::FT6X36(i2c_port_t port, gpio_num_t interruptPin)
{
_instance = this;
_port = port;
_intPin = interruptPin;
}
// Destructor should detach interrupt to the pin
FT6X36::~FT6X36()
{
if (_intPin >= 0)
gpio_isr_handler_remove((gpio_num_t)_intPin);
}
bool FT6X36::begin(uint8_t threshold, uint16_t width, uint16_t height)
{
_touch_width = width;
_touch_height = height;
if (width == 0 || height ==0) {
ESP_LOGE(TAG,"begin(uint8_t threshold, uint16_t width, uint16_t height) did not receive the width / height so touch cannot be rotation aware");
}
uint8_t data_panel_id;
readRegister8(FT6X36_REG_PANEL_ID, &data_panel_id);
if (data_panel_id != FT6X36_VENDID) {
ESP_LOGE(TAG,"FT6X36_VENDID does not match. Received:0x%x Expected:0x%x\n",data_panel_id,FT6X36_VENDID);
return false;
}
ESP_LOGI(TAG, "\tDevice ID: 0x%02x", data_panel_id);
uint8_t chip_id;
readRegister8(FT6X36_REG_CHIPID, &chip_id);
if (chip_id != FT6206_CHIPID && chip_id != FT6236_CHIPID && chip_id != FT6336_CHIPID) {
ESP_LOGE(TAG,"FT6206_CHIPID does not match. Received:0x%x\n",chip_id);
return false;
}
ESP_LOGI(TAG, "\tFound touch controller with Chip ID: 0x%02x", chip_id);
if (_intPin >= 0)
{
// INT pin triggers the callback function on the Falling edge of the GPIO
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_NEGEDGE; // GPIO_INTR_NEGEDGE repeats always interrupt
io_conf.pin_bit_mask = 1ULL<<_intPin;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pull_down_en = (gpio_pulldown_t) 0; // disable pull-down mode
io_conf.pull_up_en = (gpio_pullup_t) 1; // pull-up mode
gpio_config(&io_conf);
esp_err_t isr_service = gpio_install_isr_service(0);
printf("ISR trigger install response: 0x%x %s\n", isr_service, (isr_service==0)?"ESP_OK":"");
gpio_isr_handler_add((gpio_num_t)_intPin, isr, (void*) 1);
}
writeRegister8(FT6X36_REG_DEVICE_MODE, 0x00);
writeRegister8(FT6X36_REG_THRESHHOLD, threshold);
writeRegister8(FT6X36_REG_TOUCHRATE_ACTIVE, 0x0E);
return true;
}
void FT6X36::registerTouchHandler(void (*fn)(TPoint point, TEvent e))
{
_touchHandler = fn;
if (CONFIG_FT6X36_DEBUG) printf("Touch handler function registered\n");
}
uint8_t FT6X36::touched()
{
uint8_t data_buf;
esp_err_t ret = readRegister8(FT6X36_REG_NUM_TOUCHES, &data_buf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Error reading from device: %s", esp_err_to_name(ret));
}
if (data_buf > 2)
{
data_buf = 0;
}
return data_buf;
}
void FT6X36::loop()
{
processTouch();
}
void IRAM_ATTR FT6X36::isr(void* arg)
{
/* Un-block the interrupt processing task now */
xSemaphoreGive(TouchSemaphore);
//xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}
void FT6X36::processTouch()
{
/* Task move to Block state to wait for interrupt event */
if (_intPin >= 0)
{
if (xSemaphoreTake(TouchSemaphore, portMAX_DELAY) == false) return;
}
readData();
uint8_t n = 0;
TRawEvent event = (TRawEvent)_touchEvent[n];
TPoint point{_touchX[n], _touchY[n]};
switch (event) {
case TRawEvent::PressDown:
_points[0] = point;
_dragMode = false;
// Note: Is in microseconds. Ref https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/esp_timer.html
_touchStartTime = esp_timer_get_time()/1000;
fireEvent(point, TEvent::TouchStart);
break;
case TRawEvent::Contact:
// Dragging makes no sense IMHO. Since the X & Y are not getting updated while dragging
// Dragging && _points[0].aboutEqual(point) - Not used IDEA 2: && (lastEvent == 2)
if (!_dragMode &&
(abs(lastX-_touchX[n]) <= maxDeviation || abs(lastY-_touchY[n])<=maxDeviation) &&
esp_timer_get_time()/1000 - _touchStartTime > 300) {
_dragMode = true;
fireEvent(point, TEvent::DragStart);
#if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1
printf("EV: DragStart\n");
#endif
} else if (_dragMode) {
fireEvent(point, TEvent::DragMove);
#if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1
printf("EV: DragMove\n");
#endif
}
fireEvent(point, TEvent::TouchMove);
// For me the _touchStartTime shouold be set in both PressDown & Contact events, but after Drag detection
_touchStartTime = esp_timer_get_time()/1000;
break;
case TRawEvent::LiftUp:
_points[9] = point;
_touchEndTime = esp_timer_get_time()/1000;
//printf("TIMEDIFF: %lu End: %lu\n", _touchEndTime - _touchStartTime, _touchEndTime);
fireEvent(point, TEvent::TouchEnd);
if (_dragMode) {
fireEvent(point, TEvent::DragEnd);
#if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1
printf("EV: DragEnd\n");
#endif
_dragMode = false;
}
if ( _touchEndTime - _touchStartTime <= 900) {
// Do not get why this: _points[0].aboutEqual(point) (Original library)
fireEvent(point, TEvent::Tap);
_points[0] = {0, 0};
_touchStartTime = 0;
#if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1
printf("EV: Tap\n");
#endif
_dragMode = false;
}
break;
case TRawEvent::NoEvent:
#if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1
printf("EV: NoEvent\n");
#endif
break;
}
// Store lastEvent
lastEvent = (int) event;
lastX = _touchX[0];
lastY = _touchY[0];
}
void FT6X36::poll(TPoint * point, TEvent * e)
{
readData();
// TPoint point{_touchX[0], _touchY[0]};
TRawEvent event = (TRawEvent)_touchEvent[0];
if (point != NULL)
{
point->x = _touchX[0];
point->y = _touchY[0];
}
if (e != NULL)
{
switch (event)
{
case TRawEvent::PressDown:
*e = TEvent::TouchStart;
break;
case TRawEvent::Contact:
*e = TEvent::TouchMove;
break;
case TRawEvent::LiftUp:
default:
*e = TEvent::TouchEnd;
break;
}
}
}
uint8_t FT6X36::read8(uint8_t regName) {
uint8_t buf;
readRegister8(regName, &buf);
return buf;
}
#define data_size 16 // Discarding last 2: 0x0E & 0x0F as not relevant
bool FT6X36::readData(void)
{
esp_err_t ret;
uint8_t data[data_size];
uint8_t touch_pnt_cnt; // Number of detected touch points
readRegister8(FT6X36_REG_NUM_TOUCHES, &touch_pnt_cnt);
// Read data
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (FT6X36_ADDR<<1), ACK_CHECK_EN);
i2c_master_write_byte(cmd, 0, ACK_CHECK_EN);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
return ret;
}
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (FT6X36_ADDR<<1)|1, ACK_CHECK_EN);
i2c_master_read(cmd, data, data_size, I2C_MASTER_LAST_NACK);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (CONFIG_FT6X36_DEBUG) {
//printf("REGISTERS:\n");
for (int16_t i = 0; i < data_size; i++)
{
printf("%x:%x ", i, data[i]);
}
printf("\n");
}
const uint8_t addrShift = 6;
// READ X, Y and Touch events (X 2)
for (uint8_t i = 0; i < 2; i++)
{
_touchX[i] = data[FT6X36_REG_P1_XH + i * addrShift] & 0x0F;
_touchX[i] <<= 8;
_touchX[i] |= data[FT6X36_REG_P1_XL + i * addrShift];
_touchY[i] = data[FT6X36_REG_P1_YH + i * addrShift] & 0x0F;
_touchY[i] <<= 8;
_touchY[i] |= data[FT6X36_REG_P1_YL + i * addrShift];
_touchEvent[i] = data[FT6X36_REG_P1_XH + i * addrShift] >> 6;
}
// Make _touchX[idx] and _touchY[idx] rotation aware
switch (_rotation)
{
case 1:
swap(_touchX[0], _touchY[0]);
swap(_touchX[1], _touchY[1]);
_touchY[0] = _touch_width - _touchY[0] -1;
_touchY[1] = _touch_width - _touchY[1] -1;
break;
case 2:
_touchX[0] = _touch_width - _touchX[0] - 1;
_touchX[1] = _touch_width - _touchX[1] - 1;
_touchY[0] = _touch_height - _touchY[0] - 1;
_touchY[1] = _touch_height - _touchY[1] - 1;
break;
case 3:
swap(_touchX[0], _touchY[0]);
swap(_touchX[1], _touchY[1]);
_touchX[0] = _touch_height - _touchX[0] - 1;
_touchX[1] = _touch_height - _touchX[1] - 1;
break;
}
if (CONFIG_FT6X36_DEBUG) {
printf("X0:%d Y0:%d EVENT:%d\n", _touchX[0], _touchY[0], _touchEvent[0]);
//printf("X1:%d Y1:%d EVENT:%d\n", _touchX[1], _touchY[1], _touchEvent[1]);
}
return true;
}
void FT6X36::writeRegister8(uint8_t reg, uint8_t value)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, FT6X36_ADDR << 1 | I2C_MASTER_WRITE, ACK_CHECK_EN);
i2c_master_write_byte(cmd, reg , ACK_CHECK_EN);
i2c_master_write_byte(cmd, value , ACK_CHECK_EN);
i2c_master_stop(cmd);
i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
}
uint8_t FT6X36::readRegister8(uint8_t reg, uint8_t *data_buf)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, FT6X36_ADDR << 1 | I2C_MASTER_WRITE, ACK_CHECK_EN);
i2c_master_write_byte(cmd, reg, I2C_MASTER_ACK);
// Research: Why it's started a 2nd time here
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (FT6X36_ADDR << 1) | I2C_MASTER_READ, true);
i2c_master_read_byte(cmd, data_buf, I2C_MASTER_NACK);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
//FT6X36_REG_GESTURE_ID. Check if it can be read!
#if defined(FT6X36_DEBUG) && FT6X36_DEBUG==1
printf("REG 0x%x: 0x%x\n",reg,ret);
#endif
return ret;
}
void FT6X36::fireEvent(TPoint point, TEvent e)
{
if (_touchHandler)
_touchHandler(point, e);
}
void FT6X36::debugInfo()
{
printf(" TH_DIFF: %d CTRL: %d\n", read8(FT6X36_REG_FILTER_COEF), read8(FT6X36_REG_CTRL));
printf(" TIMEENTERMONITOR: %d PERIODACTIVE: %d\n", read8(FT6X36_REG_TIME_ENTER_MONITOR), read8(FT6X36_REG_TOUCHRATE_ACTIVE));
printf(" PERIODMONITOR: %d RADIAN_VALUE: %d\n", read8(FT6X36_REG_TOUCHRATE_MONITOR), read8(FT6X36_REG_RADIAN_VALUE));
printf(" OFFSET_LEFT_RIGHT: %d OFFSET_UP_DOWN: %d\n", read8(FT6X36_REG_OFFSET_LEFT_RIGHT), read8(FT6X36_REG_OFFSET_UP_DOWN));
printf("DISTANCE_LEFT_RIGHT: %d DISTANCE_UP_DOWN: %d\n", read8(FT6X36_REG_DISTANCE_LEFT_RIGHT), read8(FT6X36_REG_DISTANCE_UP_DOWN));
printf(" DISTANCE_ZOOM: %d CIPHER: %d\n", read8(FT6X36_REG_DISTANCE_ZOOM), read8(FT6X36_REG_CHIPID));
printf(" G_MODE: %d PWR_MODE: %d\n", read8(FT6X36_REG_INTERRUPT_MODE), read8(FT6X36_REG_POWER_MODE));
printf(" FIRMID: %d FOCALTECH_ID: %d STATE: %d\n", read8(FT6X36_REG_FIRMWARE_VERSION), read8(FT6X36_REG_PANEL_ID), read8(FT6X36_REG_STATE));
}
void FT6X36::setRotation(uint8_t rotation) {
_rotation = rotation;
}
void FT6X36::setTouchWidth(uint16_t width) {
printf("touch w:%d\n",width);
_touch_width = width;
}
void FT6X36::setTouchHeight(uint16_t height) {
printf("touch h:%d\n",height);
_touch_height = height;
}

View File

@ -0,0 +1,184 @@
#include <stdint.h>
#include <cstdlib>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include <stdio.h>
#include "esp_log.h"
#include "driver/i2c.h"
#include "sdkconfig.h"
#include <esp_timer.h>
#ifndef ft6x36_h
#define ft6x36_h
// I2C Constants
#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/
#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */
#define ACK_VAL 0x0 /*!< I2C ack value */
#define NACK_VAL 0x1 /*!< I2C nack value */
//SemaphoreHandle_t print_mux = NULL;
#define FT6X36_ADDR 0x38
#define FT6X36_REG_DEVICE_MODE 0x00
#define FT6X36_REG_GESTURE_ID 0x01
#define FT6X36_REG_NUM_TOUCHES 0x02
#define FT6X36_REG_P1_XH 0x03
#define FT6X36_REG_P1_XL 0x04
#define FT6X36_REG_P1_YH 0x05
#define FT6X36_REG_P1_YL 0x06
#define FT6X36_REG_P1_WEIGHT 0x07
#define FT6X36_REG_P1_MISC 0x08
#define FT6X36_REG_P2_XH 0x09
#define FT6X36_REG_P2_XL 0x0A
#define FT6X36_REG_P2_YH 0x0B
#define FT6X36_REG_P2_YL 0x0C
#define FT6X36_REG_P2_WEIGHT 0x0D
#define FT6X36_REG_P2_MISC 0x0E
#define FT6X36_REG_THRESHHOLD 0x80
#define FT6X36_REG_FILTER_COEF 0x85
#define FT6X36_REG_CTRL 0x86
#define FT6X36_REG_TIME_ENTER_MONITOR 0x87
#define FT6X36_REG_TOUCHRATE_ACTIVE 0x88
#define FT6X36_REG_TOUCHRATE_MONITOR 0x89 // value in ms
#define FT6X36_REG_RADIAN_VALUE 0x91
#define FT6X36_REG_OFFSET_LEFT_RIGHT 0x92
#define FT6X36_REG_OFFSET_UP_DOWN 0x93
#define FT6X36_REG_DISTANCE_LEFT_RIGHT 0x94
#define FT6X36_REG_DISTANCE_UP_DOWN 0x95
#define FT6X36_REG_DISTANCE_ZOOM 0x96
#define FT6X36_REG_LIB_VERSION_H 0xA1
#define FT6X36_REG_LIB_VERSION_L 0xA2
#define FT6X36_REG_CHIPID 0xA3
#define FT6X36_REG_INTERRUPT_MODE 0xA4
#define FT6X36_REG_POWER_MODE 0xA5
#define FT6X36_REG_FIRMWARE_VERSION 0xA6
#define FT6X36_REG_PANEL_ID 0xA8
#define FT6X36_REG_STATE 0xBC
#define FT6X36_PMODE_ACTIVE 0x00
#define FT6X36_PMODE_MONITOR 0x01
#define FT6X36_PMODE_STANDBY 0x02
#define FT6X36_PMODE_HIBERNATE 0x03
/* Possible values returned by FT6X36_GEST_ID_REG */
#define FT6X36_GEST_ID_NO_GESTURE 0x00
#define FT6X36_GEST_ID_MOVE_UP 0x10
#define FT6X36_GEST_ID_MOVE_RIGHT 0x14
#define FT6X36_GEST_ID_MOVE_DOWN 0x18
#define FT6X36_GEST_ID_MOVE_LEFT 0x1C
#define FT6X36_GEST_ID_ZOOM_IN 0x48
#define FT6X36_GEST_ID_ZOOM_OUT 0x49
#define FT6X36_VENDID 0x11
#define FT6206_CHIPID 0x06
#define FT6236_CHIPID 0x36
#define FT6336_CHIPID 0x64
#define FT6X36_DEFAULT_THRESHOLD 22
// From: https://github.com/lvgl/lv_port_esp32/blob/master/components/lvgl_esp32_drivers/lvgl_touch/ft6x36.h
#define FT6X36_MSB_MASK 0x0F
#define FT6X36_LSB_MASK 0xFF
enum class TRawEvent
{
PressDown,
LiftUp,
Contact,
NoEvent
};
enum class TEvent
{
None,
TouchStart,
TouchMove,
TouchEnd,
Tap,
DragStart,
DragMove,
DragEnd
};
struct TPoint
{
uint16_t x;
uint16_t y;
/**
* This is being used in the original library but I'm not using it in this implementation
*/
bool aboutEqual(const TPoint point)
{
return abs(x - point.x) <= 5 && abs(y - point.y) <= 5;
}
};
class FT6X36
{
static void IRAM_ATTR isr(void* arg);
public:
// TwoWire * wire will be replaced by ESP-IDF https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2c.html
FT6X36(i2c_port_t = I2C_NUM_0, gpio_num_t interruptPin = GPIO_NUM_NC);
~FT6X36();
bool begin(uint8_t threshold = FT6X36_DEFAULT_THRESHOLD, uint16_t width = 0, uint16_t height = 0);
void registerTouchHandler(void(*fn)(TPoint point, TEvent e));
uint8_t touched();
void loop();
void processTouch();
void debugInfo();
void poll(TPoint * point, TEvent * event);
// Helper functions to make the touch display aware
void setRotation(uint8_t rotation);
void setTouchWidth(uint16_t width);
void setTouchHeight(uint16_t height);
// Pending implementation. How much x->touch y↓touch is placed (In case is smaller than display)
void setXoffset(uint16_t x_offset);
void setYoffset(uint16_t y_offset);
// Smart template from EPD to swap x,y:
template <typename T> static inline void
swap(T& a, T& b)
{
T t = a;
a = b;
b = t;
}
void(*_touchHandler)(TPoint point, TEvent e) = nullptr;
bool readData(void);
private:
void writeRegister8(uint8_t reg, uint8_t val);
uint8_t readRegister8(uint8_t reg, uint8_t *data_buf);
void fireEvent(TPoint point, TEvent e);
uint8_t read8(uint8_t regName);
static FT6X36 * _instance;
i2c_port_t _port;
int8_t _intPin;
// Make touch rotation aware:
uint8_t _rotation = 0;
uint16_t _touch_width = 0;
uint16_t _touch_height = 0;
uint8_t _touches;
uint16_t _touchX[2], _touchY[2], _touchEvent[2];
TPoint _points[10];
uint8_t _pointIdx = 0;
unsigned long _touchStartTime = 0;
unsigned long _touchEndTime = 0;
uint8_t lastEvent = 3; // No event
uint16_t lastX = 0;
uint16_t lastY = 0;
bool _dragMode = false;
const uint8_t maxDeviation = 5;
};
#endif

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 strange_v
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,8 @@
This project is an adaption of the code at https://github.com/martinberlin/FT6X36-IDF which is an adaptation of https://github.com/strange-v/FT6X36
The original license is an MIT license and is included in this directory.
Changes:
- Remove Kconfig-based configuratio
- Removed I2C init code
- Allow for passing a different I2C port

View File

@ -0,0 +1,164 @@
#include "Core2Display.h"
#include "Core2DisplayConstants.h"
#include "Log.h"
#include <TactilityCore.h>
#include <esp_lcd_panel_commands.h>
#include "driver/gpio.h"
#include "esp_err.h"
#include "esp_lcd_ili9341.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lvgl_port.h"
#include "Core2Touch.h"
#define TAG "yellow_display"
bool Core2Display::start() {
TT_LOG_I(TAG, "Starting");
const esp_lcd_panel_io_spi_config_t panel_io_config = ILI9341_PANEL_IO_SPI_CONFIG(
CORE2_LCD_PIN_CS,
CORE2_LCD_PIN_DC,
nullptr,
nullptr
);
if (esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)CORE2_LCD_SPI_HOST, &panel_io_config, &ioHandle) != ESP_OK) {
TT_LOG_E(TAG, "Failed to create panel");
return false;
}
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = GPIO_NUM_NC,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
.bits_per_pixel = CORE2_LCD_BITS_PER_PIXEL,
.flags = {
.reset_active_high = false
},
.vendor_config = nullptr
};
if (esp_lcd_new_panel_ili9341(ioHandle, &panel_config, &panelHandle) != ESP_OK) {
TT_LOG_E(TAG, "Failed to create ili9341");
return false;
}
if (esp_lcd_panel_reset(panelHandle) != ESP_OK) {
TT_LOG_E(TAG, "Failed to reset panel");
return false;
}
if (esp_lcd_panel_init(panelHandle) != ESP_OK) {
TT_LOG_E(TAG, "Failed to init panel");
return false;
}
if (esp_lcd_panel_mirror(panelHandle, false, false) != ESP_OK) {
TT_LOG_E(TAG, "Failed to set panel to mirror");
return false;
}
if (esp_lcd_panel_invert_color(panelHandle, true) != ESP_OK) {
TT_LOG_E(TAG, "Failed to set panel to invert");
return false;
}
if (esp_lcd_panel_disp_on_off(panelHandle, true) != ESP_OK) {
TT_LOG_E(TAG, "Failed to turn display on");
return false;
}
const lvgl_port_display_cfg_t disp_cfg = {
.io_handle = ioHandle,
.panel_handle = panelHandle,
.control_handle = nullptr,
.buffer_size = CORE2_LCD_DRAW_BUFFER_SIZE,
.double_buffer = true,
.trans_size = 0,
.hres = CORE2_LCD_HORIZONTAL_RESOLUTION,
.vres = CORE2_LCD_VERTICAL_RESOLUTION,
.monochrome = false,
.rotation = {
.swap_xy = false,
.mirror_x = false,
.mirror_y = false,
},
.color_format = LV_COLOR_FORMAT_RGB565,
.flags = {
.buff_dma = false,
.buff_spiram = true,
.sw_rotate = false,
.swap_bytes = true,
.full_refresh = false,
.direct_mode = false
}
};
displayHandle = lvgl_port_add_disp(&disp_cfg);
TT_LOG_I(TAG, "Finished");
return displayHandle != nullptr;
}
bool Core2Display::stop() {
tt_assert(displayHandle != nullptr);
lvgl_port_remove_disp(displayHandle);
if (esp_lcd_panel_del(panelHandle) != ESP_OK) {
return false;
}
if (esp_lcd_panel_io_del(ioHandle) != ESP_OK) {
return false;
}
displayHandle = nullptr;
return true;
}
/**
* Note:
* The datasheet implies this should work, but it doesn't:
* https://www.digikey.com/htmldatasheets/production/1640716/0/0/1/ILI9341-Datasheet.pdf
*
* This repo claims it only has 1 curve:
* https://github.com/brucemack/hello-ili9341
*
* I'm leaving it in as I'm not sure if it's just my hardware that's problematic.
*/
void Core2Display::setGammaCurve(uint8_t index) {
uint8_t gamma_curve;
switch (index) {
case 0:
gamma_curve = 0x01;
break;
case 1:
gamma_curve = 0x04;
break;
case 2:
gamma_curve = 0x02;
break;
case 3:
gamma_curve = 0x08;
break;
default:
return;
}
const uint8_t param[] = {
gamma_curve
};
if (esp_lcd_panel_io_tx_param(ioHandle , LCD_CMD_GAMSET, param, 1) != ESP_OK) {
TT_LOG_E(TAG, "Failed to set gamma");
}
}
tt::hal::Touch* _Nullable Core2Display::createTouch() {
return static_cast<tt::hal::Touch*>(new Core2Touch());
}
tt::hal::Display* createDisplay() {
return static_cast<tt::hal::Display*>(new Core2Display());
}

View File

@ -0,0 +1,34 @@
#pragma once
#include "lvgl.h"
#include "hal/Display.h"
#include <esp_lcd_types.h>
extern lv_disp_t* displayHandle;
class Core2Display : public tt::hal::Display {
private:
esp_lcd_panel_io_handle_t ioHandle = nullptr;
esp_lcd_panel_handle_t panelHandle = nullptr;
lv_display_t* displayHandle = nullptr;
public:
bool start() override;
bool stop() override;
tt::hal::Touch* _Nullable createTouch() override;
bool supportsBacklightDuty() const override { return false; }
void setGammaCurve(uint8_t index) override;
uint8_t getGammaCurveCount() const override { return 4; };
lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; }
};
tt::hal::Display* createDisplay();

View File

@ -0,0 +1,11 @@
#pragma once
// Display
#define CORE2_LCD_SPI_HOST SPI2_HOST
#define CORE2_LCD_PIN_CS GPIO_NUM_5
#define CORE2_LCD_PIN_DC GPIO_NUM_15
#define CORE2_LCD_HORIZONTAL_RESOLUTION 320
#define CORE2_LCD_VERTICAL_RESOLUTION 240
#define CORE2_LCD_BITS_PER_PIXEL 16
#define CORE2_LCD_DRAW_BUFFER_HEIGHT (CORE2_LCD_VERTICAL_RESOLUTION / 10)
#define CORE2_LCD_DRAW_BUFFER_SIZE (CORE2_LCD_HORIZONTAL_RESOLUTION * CORE2_LCD_DRAW_BUFFER_HEIGHT * (CORE2_LCD_BITS_PER_PIXEL / 8))

View File

@ -0,0 +1,63 @@
#include "Core2Power.h"
#include "utility/AXP192_Class.hpp"
#include "TactilityCore.h"
#define TAG "core2_power"
extern m5::AXP192_Class axpDevice;
bool Core2Power::supportsMetric(MetricType type) const {
switch (type) {
case BATTERY_VOLTAGE:
case CHARGE_LEVEL:
case IS_CHARGING:
return true;
case CURRENT:
return false;
}
return false; // Safety guard for when new enum values are introduced
}
bool Core2Power::getMetric(Power::MetricType type, Power::MetricData& data) {
switch (type) {
case BATTERY_VOLTAGE:
data.valueAsUint32 = (uint32_t)TT_MAX((axpDevice.getBatteryVoltage() * 1000.f), 0.0f);
return true;
case CHARGE_LEVEL: {
auto level = axpDevice.getBatteryLevel();
if (level > 0) {
data.valueAsUint8 = (uint8_t)level;
return true;
} else {
return false;
}
}
case IS_CHARGING:
data.valueAsBool = axpDevice.isCharging();
return true;
case CURRENT:
return false;
}
return false; // Safety guard for when new enum values are introduced
}
bool Core2Power::isAllowedToCharge() const {
return allowedToCharge;
}
void Core2Power::setAllowedToCharge(bool canCharge) {
allowedToCharge = canCharge;
axpDevice.setBatteryCharge(canCharge);
}
static std::shared_ptr<Power> power;
std::shared_ptr<Power> createPower() {
if (power == nullptr) {
power = std::make_shared<Core2Power>();
}
return power;
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "hal/Power.h"
#include <memory>
using namespace tt::hal;
class Core2Power : public Power {
private:
bool allowedToCharge = true;
public:
Core2Power() = default;
~Core2Power() override = default;
bool supportsMetric(MetricType type) const override;
bool getMetric(Power::MetricType type, Power::MetricData& data) override;
bool supportsChargeControl() const override { return true; }
bool isAllowedToCharge() const override;
void setAllowedToCharge(bool canCharge) override;
};
std::shared_ptr<Power> createPower();

View File

@ -0,0 +1,31 @@
#include "Core2SdCard.h"
#include "lvgl/LvglSync.h"
#include "hal/SpiSdCard.h"
#include <esp_vfs_fat.h>
#define CORE2_SDCARD_SPI_FREQUENCY 800000U
#define CORE2_SDCARD_PIN_CS GPIO_NUM_4
#define CORE2_LCD_PIN_CS GPIO_NUM_5
std::shared_ptr<SdCard> createSdCard() {
auto* configuration = new tt::hal::SpiSdCard::Config(
CORE2_SDCARD_SPI_FREQUENCY,
CORE2_SDCARD_PIN_CS,
GPIO_NUM_NC,
GPIO_NUM_NC,
GPIO_NUM_NC,
SdCard::MountBehaviourAtBoot,
tt::lvgl::getLvglSyncLockable(),
{
CORE2_LCD_PIN_CS
}
);
auto* sdcard = (SdCard*) new SpiSdCard(
std::unique_ptr<SpiSdCard::Config>(configuration)
);
return std::shared_ptr<SdCard>(sdcard);
}

View File

@ -0,0 +1,7 @@
#pragma once
#include "hal/SdCard.h"
using namespace tt::hal;
std::shared_ptr<SdCard> createSdCard();

View File

@ -0,0 +1,94 @@
#include "Core2Touch.h"
#include <esp_lvgl_port.h>
#define TAG "core2_touch"
static void touchReadCallback(lv_indev_t* indev, lv_indev_data_t* data) {
auto* touch = (Core2Touch*)lv_indev_get_driver_data(indev);
touch->readLast(data);
}
static int32_t threadCallback(void* context) {
auto* touch = (Core2Touch*)context;
touch->driverThreadMain();
return 0;
}
Core2Touch::Core2Touch() :
driverThread(tt::Thread("core2_touch", 4096, threadCallback, this))
{ }
void Core2Touch::driverThreadMain() {
TPoint point = { .x = 0, .y = 0 };
TEvent event = TEvent::None;
while (!shouldInterruptDriverThread()) {
driver.processTouch();
driver.poll(&point, &event);
if (mutex.lock(100)) {
switch (event) {
case TEvent::TouchStart:
case TEvent::TouchMove:
case TEvent::DragStart:
case TEvent::DragMove:
case TEvent::DragEnd:
lastState = LV_INDEV_STATE_PR;
lastPoint.x = point.x;
lastPoint.y = point.y;
break;
case TEvent::TouchEnd:
lastState = LV_INDEV_STATE_REL;
lastPoint.x = point.x;
lastPoint.y = point.y;
break;
case TEvent::Tap:
case TEvent::None:
break;
}
mutex.unlock();
}
}
}
bool Core2Touch::shouldInterruptDriverThread() {
bool interrupt = false;
if (mutex.lock(50 / portTICK_PERIOD_MS)) {
interrupt = interruptDriverThread;
mutex.unlock();
}
return interrupt;
}
bool Core2Touch::start(lv_display_t* display) {
TT_LOG_I(TAG, "start");
driverThread.start();
uint16_t width = lv_display_get_horizontal_resolution(display);
uint16_t height = lv_display_get_vertical_resolution(display);
if (!driver.begin(FT6X36_DEFAULT_THRESHOLD, width, height)) {
TT_LOG_E(TAG, "driver.begin() failed");
return false;
}
deviceHandle = lv_indev_create();
lv_indev_set_type(deviceHandle, LV_INDEV_TYPE_POINTER);
lv_indev_set_driver_data(deviceHandle, this);
lv_indev_set_read_cb(deviceHandle, touchReadCallback);
TT_LOG_I(TAG, "start success");
return true;
}
bool Core2Touch::stop() {
lv_indev_delete(deviceHandle);
interruptDriverThread = true;
driverThread.join();
return true;
}
void Core2Touch::readLast(lv_indev_data_t* data) {
data->point = lastPoint;
data->state = lastState;
}

View File

@ -0,0 +1,32 @@
#pragma once
#include "hal/Touch.h"
#include "TactilityCore.h"
#include "ft6x36/FT6X36.h"
class Core2Touch : public tt::hal::Touch {
private:
lv_indev_t* _Nullable deviceHandle = nullptr;
FT6X36 driver = FT6X36(I2C_NUM_0, GPIO_NUM_39);
tt::Thread driverThread;
bool interruptDriverThread = false;
tt::Mutex mutex;
lv_point_t lastPoint = { .x = 0, .y = 0 };
lv_indev_state_t lastState = LV_INDEV_STATE_RELEASED;
bool shouldInterruptDriverThread();
public:
Core2Touch();
bool start(lv_display_t* display) override;
bool stop() override;
void readLast(lv_indev_data_t* data);
lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; }
void driverThreadMain();
};

View File

@ -17,10 +17,6 @@ public:
bool stop() override; bool stop() override;
void setPowerOn(bool turnOn) override {}
bool isPoweredOn() const override { return true; };
bool supportsPowerControl() const override { return false; }
tt::hal::Touch* _Nullable createTouch() override; tt::hal::Touch* _Nullable createTouch() override;
lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; } lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; }

View File

@ -13,10 +13,6 @@ public:
bool stop() override { tt_crash("Not supported"); } bool stop() override { tt_crash("Not supported"); }
void setPowerOn(bool turnOn) override {}
bool isPoweredOn() const override { return displayHandle != nullptr; }
bool supportsPowerControl() const override { return false; }
tt::hal::Touch* _Nullable createTouch() override { return dynamic_cast<tt::hal::Touch*>(new SdlTouch()); } tt::hal::Touch* _Nullable createTouch() override { return dynamic_cast<tt::hal::Touch*>(new SdlTouch()); }
lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; } lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; }

View File

@ -7,7 +7,7 @@
bool twodotfour_lvgl_init() { bool twodotfour_lvgl_init() {
const lvgl_port_cfg_t lvgl_cfg = { const lvgl_port_cfg_t lvgl_cfg = {
.task_priority = tt::Thread::PriorityHigh, .task_priority = tt::THREAD_PRIORITY_RENDER,
.task_stack = 8096, .task_stack = 8096,
.task_affinity = -1, // core pinning .task_affinity = -1, // core pinning
.task_max_sleep_ms = 500, .task_max_sleep_ms = 500,

View File

@ -23,7 +23,8 @@ static bool initBacklight() {
.duty_resolution = TWODOTFOUR_LCD_BACKLIGHT_LEDC_DUTY_RES, .duty_resolution = TWODOTFOUR_LCD_BACKLIGHT_LEDC_DUTY_RES,
.timer_num = TWODOTFOUR_LCD_BACKLIGHT_LEDC_TIMER, .timer_num = TWODOTFOUR_LCD_BACKLIGHT_LEDC_TIMER,
.freq_hz = TWODOTFOUR_LCD_BACKLIGHT_LEDC_FREQUENCY, .freq_hz = TWODOTFOUR_LCD_BACKLIGHT_LEDC_FREQUENCY,
.clk_cfg = LEDC_AUTO_CLK .clk_cfg = LEDC_AUTO_CLK,
.deconfigure = false
}; };
if (ledc_timer_config(&ledc_timer) != ESP_OK) { if (ledc_timer_config(&ledc_timer) != ESP_OK) {

View File

@ -20,10 +20,6 @@ public:
bool stop() override; bool stop() override;
void setPowerOn(bool turnOn) override {}
bool isPoweredOn() const override { return true; };
bool supportsPowerControl() const override { return false; }
tt::hal::Touch* _Nullable createTouch() override; tt::hal::Touch* _Nullable createTouch() override;
void setBacklightDuty(uint8_t backlightDuty) override; void setBacklightDuty(uint8_t backlightDuty) override;

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
// Dipslay backlight (PWM) // Display backlight (PWM)
#define TWODOTFOUR_LCD_BACKLIGHT_LEDC_TIMER LEDC_TIMER_0 #define TWODOTFOUR_LCD_BACKLIGHT_LEDC_TIMER LEDC_TIMER_0
#define TWODOTFOUR_LCD_BACKLIGHT_LEDC_MODE LEDC_LOW_SPEED_MODE #define TWODOTFOUR_LCD_BACKLIGHT_LEDC_MODE LEDC_LOW_SPEED_MODE
#define TWODOTFOUR_LCD_BACKLIGHT_LEDC_CHANNEL LEDC_CHANNEL_0 #define TWODOTFOUR_LCD_BACKLIGHT_LEDC_CHANNEL LEDC_CHANNEL_0

View File

@ -7,7 +7,7 @@
#include "esp_lcd_touch.h" #include "esp_lcd_touch.h"
#include "esp_lvgl_port.h" #include "esp_lvgl_port.h"
#define TAG "m5stack_touch" #define TAG "twodotfour_touch"
bool YellowTouch::start(lv_display_t* display) { bool YellowTouch::start(lv_display_t* display) {
TT_LOG_I(TAG, "Starting"); TT_LOG_I(TAG, "Starting");

View File

@ -35,12 +35,12 @@ typedef struct {
PubSubSubscription* pubsub_subscription; PubSubSubscription* pubsub_subscription;
} Statusbar; } Statusbar;
static void statusbar_lock() { static bool statusbar_lock(uint32_t timeoutTicks) {
statusbar_data.mutex.acquire(TtWaitForever); return statusbar_data.mutex.lock(timeoutTicks);
} }
static void statusbar_unlock() { static bool statusbar_unlock() {
statusbar_data.mutex.release(); return statusbar_data.mutex.unlock();
} }
static void statusbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj); static void statusbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj);
@ -113,7 +113,7 @@ lv_obj_t* statusbar_create(lv_obj_t* parent) {
lv_obj_t* left_spacer = spacer_create(obj, 1, 1); lv_obj_t* left_spacer = spacer_create(obj, 1, 1);
lv_obj_set_flex_grow(left_spacer, 1); lv_obj_set_flex_grow(left_spacer, 1);
statusbar_lock(); statusbar_lock(TtWaitForever);
for (int i = 0; i < STATUSBAR_ICON_LIMIT; ++i) { for (int i = 0; i < STATUSBAR_ICON_LIMIT; ++i) {
lv_obj_t* image = lv_image_create(obj); lv_obj_t* image = lv_image_create(obj);
lv_obj_set_size(image, STATUSBAR_ICON_SIZE, STATUSBAR_ICON_SIZE); lv_obj_set_size(image, STATUSBAR_ICON_SIZE, STATUSBAR_ICON_SIZE);
@ -129,11 +129,12 @@ lv_obj_t* statusbar_create(lv_obj_t* parent) {
} }
static void update_main(Statusbar* statusbar) { static void update_main(Statusbar* statusbar) {
statusbar_lock(); if (statusbar_lock(50 / portTICK_PERIOD_MS)) {
for (int i = 0; i < STATUSBAR_ICON_LIMIT; ++i) { for (int i = 0; i < STATUSBAR_ICON_LIMIT; ++i) {
update_icon(statusbar->icons[i], &(statusbar_data.icons[i])); update_icon(statusbar->icons[i], &(statusbar_data.icons[i]));
}
statusbar_unlock();
} }
statusbar_unlock();
} }
static void statusbar_event(TT_UNUSED const lv_obj_class_t* class_p, lv_event_t* event) { static void statusbar_event(TT_UNUSED const lv_obj_class_t* class_p, lv_event_t* event) {
@ -154,7 +155,7 @@ static void statusbar_event(TT_UNUSED const lv_obj_class_t* class_p, lv_event_t*
} }
int8_t statusbar_icon_add(const char* _Nullable image) { int8_t statusbar_icon_add(const char* _Nullable image) {
statusbar_lock(); statusbar_lock(TtWaitForever);
int8_t result = -1; int8_t result = -1;
for (int8_t i = 0; i < STATUSBAR_ICON_LIMIT; ++i) { for (int8_t i = 0; i < STATUSBAR_ICON_LIMIT; ++i) {
if (!statusbar_data.icons[i].claimed) { if (!statusbar_data.icons[i].claimed) {
@ -174,7 +175,7 @@ int8_t statusbar_icon_add(const char* _Nullable image) {
void statusbar_icon_remove(int8_t id) { void statusbar_icon_remove(int8_t id) {
TT_LOG_I(TAG, "id %d: remove", id); TT_LOG_I(TAG, "id %d: remove", id);
tt_check(id >= 0 && id < STATUSBAR_ICON_LIMIT); tt_check(id >= 0 && id < STATUSBAR_ICON_LIMIT);
statusbar_lock(); statusbar_lock(TtWaitForever);
StatusbarIcon* icon = &statusbar_data.icons[id]; StatusbarIcon* icon = &statusbar_data.icons[id];
icon->claimed = false; icon->claimed = false;
icon->visible = false; icon->visible = false;
@ -184,25 +185,27 @@ void statusbar_icon_remove(int8_t id) {
} }
void statusbar_icon_set_image(int8_t id, const char* image) { void statusbar_icon_set_image(int8_t id, const char* image) {
TT_LOG_I(TAG, "id %d: set image %s", id, image); TT_LOG_I(TAG, "id %d: set image %s", id, image ? image : "(none)");
tt_check(id >= 0 && id < STATUSBAR_ICON_LIMIT); tt_check(id >= 0 && id < STATUSBAR_ICON_LIMIT);
statusbar_lock(); if (statusbar_lock(50 / portTICK_PERIOD_MS)) {
StatusbarIcon* icon = &statusbar_data.icons[id]; StatusbarIcon* icon = &statusbar_data.icons[id];
tt_check(icon->claimed); tt_check(icon->claimed);
icon->image = image; icon->image = image;
tt_pubsub_publish(statusbar_data.pubsub, nullptr); tt_pubsub_publish(statusbar_data.pubsub, nullptr);
statusbar_unlock(); statusbar_unlock();
}
} }
void statusbar_icon_set_visibility(int8_t id, bool visible) { void statusbar_icon_set_visibility(int8_t id, bool visible) {
TT_LOG_I(TAG, "id %d: set visibility %d", id, visible); TT_LOG_I(TAG, "id %d: set visibility %d", id, visible);
tt_check(id >= 0 && id < STATUSBAR_ICON_LIMIT); tt_check(id >= 0 && id < STATUSBAR_ICON_LIMIT);
statusbar_lock(); if (statusbar_lock(50 / portTICK_PERIOD_MS)) {
StatusbarIcon* icon = &statusbar_data.icons[id]; StatusbarIcon* icon = &statusbar_data.icons[id];
tt_check(icon->claimed); tt_check(icon->claimed);
icon->visible = visible; icon->visible = visible;
tt_pubsub_publish(statusbar_data.pubsub, nullptr); tt_pubsub_publish(statusbar_data.pubsub, nullptr);
statusbar_unlock(); statusbar_unlock();
}
} }
} // namespace } // namespace

View File

@ -10,6 +10,7 @@
#include "service/wifi/Wifi.h" #include "service/wifi/Wifi.h"
#include "service/ServiceRegistry.h" #include "service/ServiceRegistry.h"
#include "TactilityHeadless.h" #include "TactilityHeadless.h"
#include "lvgl/LvglSync.h"
namespace tt::service::statusbar { namespace tt::service::statusbar {
@ -182,7 +183,10 @@ static void onUpdate(std::shared_ptr<void> parameter) {
} }
static void onStart(ServiceContext& service) { static void onStart(ServiceContext& service) {
lvgl::lock(TtWaitForever);
auto data = std::make_shared<ServiceData>(); auto data = std::make_shared<ServiceData>();
lvgl::unlock();
service.setData(data); service.setData(data);
// TODO: Make thread-safe for LVGL // TODO: Make thread-safe for LVGL

View File

@ -6,6 +6,7 @@ namespace tt {
#define TAG "Dispatcher" #define TAG "Dispatcher"
#define BACKPRESSURE_WARNING_COUNT 100 #define BACKPRESSURE_WARNING_COUNT 100
#define WAIT_FLAG 1
Dispatcher::Dispatcher() : Dispatcher::Dispatcher() :
mutex(Mutex::TypeNormal) mutex(Mutex::TypeNormal)
@ -22,13 +23,12 @@ void Dispatcher::dispatch(Callback callback, std::shared_ptr<void> context) {
// Mutate // Mutate
if (mutex.lock(1000 / portTICK_PERIOD_MS)) { if (mutex.lock(1000 / portTICK_PERIOD_MS)) {
queue.push(std::move(message)); queue.push(std::move(message));
TT_LOG_I(TAG, "dispatch");
if (queue.size() == BACKPRESSURE_WARNING_COUNT) { if (queue.size() == BACKPRESSURE_WARNING_COUNT) {
TT_LOG_W(TAG, "Backpressure: You're not consuming fast enough (100 queued)"); TT_LOG_W(TAG, "Backpressure: You're not consuming fast enough (100 queued)");
} }
mutex.unlock(); tt_check(mutex.unlock());
// Signal // Signal
eventFlag.set(1); eventFlag.set(WAIT_FLAG);
} else { } else {
TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
} }
@ -37,15 +37,15 @@ void Dispatcher::dispatch(Callback callback, std::shared_ptr<void> context) {
uint32_t Dispatcher::consume(uint32_t timeout_ticks) { uint32_t Dispatcher::consume(uint32_t timeout_ticks) {
// Wait for signal and clear // Wait for signal and clear
TickType_t start_ticks = kernel::getTicks(); TickType_t start_ticks = kernel::getTicks();
if (eventFlag.wait(1, TtFlagWaitAny, timeout_ticks) == TtStatusErrorTimeout) { if (eventFlag.wait(WAIT_FLAG, TtFlagWaitAny, timeout_ticks) == WAIT_FLAG) {
eventFlag.clear(WAIT_FLAG);
} else {
return 0; return 0;
} }
TickType_t ticks_remaining = TT_MAX(timeout_ticks - (kernel::getTicks() - start_ticks), 0); TickType_t ticks_remaining = TT_MAX(timeout_ticks - (kernel::getTicks() - start_ticks), 0);
eventFlag.clear(1); TT_LOG_I(TAG, "Dispatcher continuing (%d ticks)", (int)ticks_remaining);
TT_LOG_I(TAG, "Dispatcher continuing");
// Mutate // Mutate
bool processing = true; bool processing = true;
@ -53,16 +53,15 @@ uint32_t Dispatcher::consume(uint32_t timeout_ticks) {
do { do {
if (mutex.lock(ticks_remaining / portTICK_PERIOD_MS)) { if (mutex.lock(ticks_remaining / portTICK_PERIOD_MS)) {
if (!queue.empty()) { if (!queue.empty()) {
TT_LOG_I(TAG, "Dispatcher popping from queue");
auto item = queue.front(); auto item = queue.front();
queue.pop(); queue.pop();
consumed++; consumed++;
processing = !queue.empty(); processing = !queue.empty();
// Don't keep lock as callback might be slow // Don't keep lock as callback might be slow
tt_check(mutex.unlock()); tt_check(mutex.unlock());
item->callback(item->context); item->callback(item->context);
} else { } else {
processing = false;
tt_check(mutex.unlock()); tt_check(mutex.unlock());
} }

View File

@ -25,7 +25,7 @@ DispatcherThread::~DispatcherThread() {
void DispatcherThread::_threadMain() { void DispatcherThread::_threadMain() {
do { do {
dispatcher.consume(1000); dispatcher.consume(1000 / portTICK_PERIOD_MS);
} while (!interruptThread); } while (!interruptThread);
} }

View File

@ -61,7 +61,6 @@ TtStatus Mutex::acquire(uint32_t timeout) const {
} else { } else {
return TtStatusOk; return TtStatusOk;
} }
break;
case TypeRecursive: case TypeRecursive:
if (xSemaphoreTakeRecursive(semaphore, timeout) != pdPASS) { if (xSemaphoreTakeRecursive(semaphore, timeout) != pdPASS) {
if (timeout != 0U) { if (timeout != 0U) {
@ -72,7 +71,6 @@ TtStatus Mutex::acquire(uint32_t timeout) const {
} else { } else {
return TtStatusOk; return TtStatusOk;
} }
break;
default: default:
tt_crash("mutex type unknown/corrupted"); tt_crash("mutex type unknown/corrupted");
} }
@ -90,7 +88,6 @@ TtStatus Mutex::release() const {
} else { } else {
return TtStatusOk; return TtStatusOk;
} }
break;
} }
case TypeRecursive: case TypeRecursive:
if (xSemaphoreGiveRecursive(semaphore) != pdPASS) { if (xSemaphoreGiveRecursive(semaphore) != pdPASS) {
@ -98,7 +95,6 @@ TtStatus Mutex::release() const {
} else { } else {
return TtStatusOk; return TtStatusOk;
} }
break;
default: default:
tt_crash("mutex type unknown/corrupted"); tt_crash("mutex type unknown/corrupted");
} }

View File

@ -2,14 +2,6 @@
#include <cstdint> #include <cstdint>
#ifndef TT_CRITICAL_ENTER
#define TT_CRITICAL_ENTER() __TtCriticalInfo __tt_critical_info = __tt_critical_enter();
#endif
#ifndef TT_CRITICAL_EXIT
#define TT_CRITICAL_EXIT() __tt_critical_exit(__tt_critical_info);
#endif
namespace tt::kernel::critical { namespace tt::kernel::critical {
typedef struct { typedef struct {

View File

@ -11,9 +11,9 @@ public:
virtual bool start() = 0; virtual bool start() = 0;
virtual bool stop() = 0; virtual bool stop() = 0;
virtual void setPowerOn(bool turnOn) = 0; virtual void setPowerOn(bool turnOn) {}
virtual bool isPoweredOn() const = 0; virtual bool isPoweredOn() const { return true; }
virtual bool supportsPowerControl() const = 0; virtual bool supportsPowerControl() const { return false; }
virtual Touch* _Nullable createTouch() = 0; virtual Touch* _Nullable createTouch() = 0;