Implemented LilyGO T-Lora Pager (#295)
This commit is contained in:
parent
ab4cf79a47
commit
00b62a2831
@ -37,6 +37,8 @@ menu "Tactility App"
|
|||||||
bool "Elecrow CrowPanel Basic 5.0"
|
bool "Elecrow CrowPanel Basic 5.0"
|
||||||
config TT_BOARD_LILYGO_TDECK
|
config TT_BOARD_LILYGO_TDECK
|
||||||
bool "LilyGo T-Deck"
|
bool "LilyGo T-Deck"
|
||||||
|
config TT_BOARD_LILYGO_TLORA_PAGER
|
||||||
|
bool "LilyGo T-Lora Pager"
|
||||||
config TT_BOARD_M5STACK_CORE2
|
config TT_BOARD_M5STACK_CORE2
|
||||||
bool "M5Stack Core2"
|
bool "M5Stack Core2"
|
||||||
config TT_BOARD_M5STACK_CORES3
|
config TT_BOARD_M5STACK_CORES3
|
||||||
|
|||||||
@ -8,6 +8,9 @@
|
|||||||
#if defined(CONFIG_TT_BOARD_LILYGO_TDECK)
|
#if defined(CONFIG_TT_BOARD_LILYGO_TDECK)
|
||||||
#include "LilygoTdeck.h"
|
#include "LilygoTdeck.h"
|
||||||
#define TT_BOARD_HARDWARE &lilygo_tdeck
|
#define TT_BOARD_HARDWARE &lilygo_tdeck
|
||||||
|
#elif defined(CONFIG_TT_BOARD_LILYGO_TLORA_PAGER)
|
||||||
|
#include "LilygoTloraPager.h"
|
||||||
|
#define TT_BOARD_HARDWARE &lilygo_tlora_pager
|
||||||
#elif defined(CONFIG_TT_BOARD_CYD_2432S024C)
|
#elif defined(CONFIG_TT_BOARD_CYD_2432S024C)
|
||||||
#include "CYD2432S024C.h"
|
#include "CYD2432S024C.h"
|
||||||
#define TT_BOARD_HARDWARE &cyd_2432s024c_config
|
#define TT_BOARD_HARDWARE &cyd_2432s024c_config
|
||||||
|
|||||||
@ -12,6 +12,8 @@ dependencies:
|
|||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
rules:
|
rules:
|
||||||
- if: "target in [esp32s3, esp32p4]"
|
- if: "target in [esp32s3, esp32p4]"
|
||||||
|
espressif/esp_lcd_st7796:
|
||||||
|
version: "1.3.2"
|
||||||
espressif/esp_lcd_panel_io_additions: "1.0.1"
|
espressif/esp_lcd_panel_io_additions: "1.0.1"
|
||||||
espressif/esp_tinyusb:
|
espressif/esp_tinyusb:
|
||||||
version: "1.5.0"
|
version: "1.5.0"
|
||||||
|
|||||||
7
Boards/LilygoTLoraPager/CMakeLists.txt
Normal file
7
Boards/LilygoTLoraPager/CMakeLists.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
||||||
|
|
||||||
|
idf_component_register(
|
||||||
|
SRCS ${SOURCE_FILES}
|
||||||
|
INCLUDE_DIRS "Source"
|
||||||
|
REQUIRES Tactility esp_lvgl_port esp_lcd ST7796 BQ27220 TCA8418 PwmBacklight driver esp_adc
|
||||||
|
)
|
||||||
55
Boards/LilygoTLoraPager/Source/Init.cpp
Normal file
55
Boards/LilygoTLoraPager/Source/Init.cpp
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#include "PwmBacklight.h"
|
||||||
|
#include "Tactility/kernel/SystemEvents.h"
|
||||||
|
#include "Tactility/service/gps/GpsService.h"
|
||||||
|
|
||||||
|
#include <Tactility/TactilityCore.h>
|
||||||
|
#include <Tactility/hal/gps/GpsConfiguration.h>
|
||||||
|
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
|
||||||
|
#include <Bq27220.h>
|
||||||
|
#include <Tca8418.h>
|
||||||
|
|
||||||
|
#define TAG "tpager"
|
||||||
|
|
||||||
|
// Power on
|
||||||
|
#define TDECK_POWERON_GPIO GPIO_NUM_10
|
||||||
|
|
||||||
|
std::shared_ptr<Bq27220> bq27220;
|
||||||
|
std::shared_ptr<Tca8418> tca8418;
|
||||||
|
|
||||||
|
bool tpagerInit() {
|
||||||
|
ESP_LOGI(TAG, LOG_MESSAGE_POWER_ON_START);
|
||||||
|
|
||||||
|
/* 32 Khz and higher gives an issue where the screen starts dimming again above 80% brightness
|
||||||
|
* when moving the brightness slider rapidly from a lower setting to 100%.
|
||||||
|
* This is not a slider bug (data was debug-traced) */
|
||||||
|
if (!driver::pwmbacklight::init(GPIO_NUM_42, 30000)) {
|
||||||
|
TT_LOG_E(TAG, "Backlight init failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bq27220 = std::make_shared<Bq27220>(I2C_NUM_0);
|
||||||
|
tt::hal::registerDevice(bq27220);
|
||||||
|
|
||||||
|
tca8418 = std::make_shared<Tca8418>(I2C_NUM_0);
|
||||||
|
tt::hal::registerDevice(tca8418);
|
||||||
|
|
||||||
|
tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) {
|
||||||
|
bq27220->configureCapacity(1500, 1500);
|
||||||
|
|
||||||
|
auto gps_service = tt::service::gps::findGpsService();
|
||||||
|
if (gps_service != nullptr) {
|
||||||
|
std::vector<tt::hal::gps::GpsConfiguration> gps_configurations;
|
||||||
|
gps_service->getGpsConfigurations(gps_configurations);
|
||||||
|
if (gps_configurations.empty()) {
|
||||||
|
if (gps_service->addGpsConfiguration(tt::hal::gps::GpsConfiguration {.uartName = "Grove", .baudRate = 38400, .model = tt::hal::gps::GpsModel::UBLOX10})) {
|
||||||
|
TT_LOG_I(TAG, "Configured internal GPS");
|
||||||
|
} else {
|
||||||
|
TT_LOG_E(TAG, "Failed to configure internal GPS");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
83
Boards/LilygoTLoraPager/Source/LilygoTloraPager.cpp
Normal file
83
Boards/LilygoTLoraPager/Source/LilygoTloraPager.cpp
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
#include "Tactility/lvgl/LvglSync.h"
|
||||||
|
#include "hal/TpagerDisplay.h"
|
||||||
|
#include "hal/TpagerDisplayConstants.h"
|
||||||
|
#include "hal/TpagerKeyboard.h"
|
||||||
|
#include "hal/TpagerPower.h"
|
||||||
|
#include "hal/TpagerSdCard.h"
|
||||||
|
|
||||||
|
#include <Tactility/hal/Configuration.h>
|
||||||
|
|
||||||
|
#define TPAGER_SPI_TRANSFER_SIZE_LIMIT (TPAGER_LCD_HORIZONTAL_RESOLUTION * TPAGER_LCD_SPI_TRANSFER_HEIGHT * (LV_COLOR_DEPTH / 8))
|
||||||
|
|
||||||
|
bool tpagerInit();
|
||||||
|
|
||||||
|
using namespace tt::hal;
|
||||||
|
|
||||||
|
extern const Configuration lilygo_tlora_pager = {
|
||||||
|
.initBoot = tpagerInit,
|
||||||
|
.createDisplay = createDisplay,
|
||||||
|
.createKeyboard = createKeyboard,
|
||||||
|
.sdcard = createTpagerSdCard(),
|
||||||
|
.power = tpager_get_power,
|
||||||
|
.i2c = {
|
||||||
|
i2c::Configuration {
|
||||||
|
.name = "Shared",
|
||||||
|
.port = I2C_NUM_0,
|
||||||
|
.initMode = i2c::InitMode::ByTactility,
|
||||||
|
.isMutable = true,
|
||||||
|
.config = (i2c_config_t) {
|
||||||
|
.mode = I2C_MODE_MASTER,
|
||||||
|
.sda_io_num = GPIO_NUM_3,
|
||||||
|
.scl_io_num = GPIO_NUM_2,
|
||||||
|
.sda_pullup_en = false,
|
||||||
|
.scl_pullup_en = false,
|
||||||
|
.master = {
|
||||||
|
.clk_speed = 100'000
|
||||||
|
},
|
||||||
|
.clk_flags = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.spi {spi::Configuration {
|
||||||
|
.device = SPI2_HOST,
|
||||||
|
.dma = SPI_DMA_CH_AUTO,
|
||||||
|
.config = {.mosi_io_num = GPIO_NUM_34, .miso_io_num = GPIO_NUM_33, .sclk_io_num = GPIO_NUM_35,
|
||||||
|
.quadwp_io_num = GPIO_NUM_NC, // Quad SPI LCD driver is not yet supported
|
||||||
|
.quadhd_io_num = GPIO_NUM_NC, // Quad SPI LCD driver is not yet supported
|
||||||
|
.data4_io_num = GPIO_NUM_NC,
|
||||||
|
.data5_io_num = GPIO_NUM_NC,
|
||||||
|
.data6_io_num = GPIO_NUM_NC,
|
||||||
|
.data7_io_num = GPIO_NUM_NC,
|
||||||
|
.data_io_default_level = false,
|
||||||
|
.max_transfer_sz = TPAGER_SPI_TRANSFER_SIZE_LIMIT,
|
||||||
|
.flags = 0,
|
||||||
|
.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO,
|
||||||
|
.intr_flags = 0},
|
||||||
|
.initMode = spi::InitMode::ByTactility,
|
||||||
|
.isMutable = false,
|
||||||
|
.lock = tt::lvgl::getSyncLock() // esp_lvgl_port owns the lock for the display
|
||||||
|
}},
|
||||||
|
.uart {uart::Configuration {
|
||||||
|
.name = "Grove",
|
||||||
|
.port = UART_NUM_1,
|
||||||
|
.rxPin = GPIO_NUM_4,
|
||||||
|
.txPin = GPIO_NUM_12,
|
||||||
|
.rtsPin = GPIO_NUM_NC,
|
||||||
|
.ctsPin = GPIO_NUM_NC,
|
||||||
|
.rxBufferSize = 1024,
|
||||||
|
.txBufferSize = 1024,
|
||||||
|
.config = {
|
||||||
|
.baud_rate = 38400,
|
||||||
|
.data_bits = UART_DATA_8_BITS,
|
||||||
|
.parity = UART_PARITY_DISABLE,
|
||||||
|
.stop_bits = UART_STOP_BITS_1,
|
||||||
|
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||||
|
.rx_flow_ctrl_thresh = 0,
|
||||||
|
.source_clk = UART_SCLK_DEFAULT,
|
||||||
|
.flags = {
|
||||||
|
.allow_pd = 0,
|
||||||
|
.backup_before_sleep = 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
};
|
||||||
5
Boards/LilygoTLoraPager/Source/LilygoTloraPager.h
Normal file
5
Boards/LilygoTLoraPager/Source/LilygoTloraPager.h
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Tactility/hal/Configuration.h>
|
||||||
|
|
||||||
|
extern const tt::hal::Configuration lilygo_tlora_pager;
|
||||||
30
Boards/LilygoTLoraPager/Source/hal/TpagerDisplay.cpp
Normal file
30
Boards/LilygoTLoraPager/Source/hal/TpagerDisplay.cpp
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#include "TpagerDisplay.h"
|
||||||
|
#include "TpagerDisplayConstants.h"
|
||||||
|
|
||||||
|
#include <PwmBacklight.h>
|
||||||
|
#include <St7796Display.h>
|
||||||
|
|
||||||
|
#include <driver/spi_master.h>
|
||||||
|
|
||||||
|
#define TAG "TPAGER_display"
|
||||||
|
|
||||||
|
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
||||||
|
auto configuration = std::make_unique<St7796Display::Configuration>(
|
||||||
|
TPAGER_LCD_SPI_HOST,
|
||||||
|
TPAGER_LCD_PIN_CS,
|
||||||
|
TPAGER_LCD_PIN_DC,
|
||||||
|
480, // w
|
||||||
|
222, // h
|
||||||
|
nullptr,
|
||||||
|
true, //swapXY
|
||||||
|
true, //mirrorX
|
||||||
|
true, //mirrorY
|
||||||
|
true, //invertColor
|
||||||
|
0, //gapX
|
||||||
|
49 //gapY
|
||||||
|
);
|
||||||
|
|
||||||
|
configuration->backlightDutyFunction = driver::pwmbacklight::setBacklightDuty;
|
||||||
|
|
||||||
|
return std::make_shared<St7796Display>(std::move(configuration));
|
||||||
|
}
|
||||||
40
Boards/LilygoTLoraPager/Source/hal/TpagerDisplay.h
Normal file
40
Boards/LilygoTLoraPager/Source/hal/TpagerDisplay.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Tactility/hal/display/DisplayDevice.h"
|
||||||
|
#include <esp_lcd_types.h>
|
||||||
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
class TpagerDisplay : public tt::hal::display::DisplayDevice {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
esp_lcd_panel_io_handle_t ioHandle = nullptr;
|
||||||
|
esp_lcd_panel_handle_t panelHandle = nullptr;
|
||||||
|
lv_display_t* displayHandle = nullptr;
|
||||||
|
bool poweredOn = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
std::string getName() const final { return "ST7796"; }
|
||||||
|
std::string getDescription() const final { return "SPI display"; }
|
||||||
|
|
||||||
|
bool start() override;
|
||||||
|
|
||||||
|
bool stop() override;
|
||||||
|
|
||||||
|
void setPowerOn(bool turnOn) override;
|
||||||
|
bool isPoweredOn() const override { return poweredOn; };
|
||||||
|
bool supportsPowerControl() const override { return true; }
|
||||||
|
|
||||||
|
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable createTouch() override;
|
||||||
|
|
||||||
|
void setBacklightDuty(uint8_t backlightDuty) override;
|
||||||
|
bool supportsBacklightDuty() const override { return true; }
|
||||||
|
|
||||||
|
void setGammaCurve(uint8_t index) override;
|
||||||
|
uint8_t getGammaCurveCount() const override { return 4; };
|
||||||
|
|
||||||
|
lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; }
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define TPAGER_LCD_SPI_HOST SPI2_HOST
|
||||||
|
#define TPAGER_LCD_PIN_CS GPIO_NUM_38
|
||||||
|
#define TPAGER_LCD_PIN_DC GPIO_NUM_37 // RS
|
||||||
|
#define TPAGER_LCD_HORIZONTAL_RESOLUTION 222
|
||||||
|
#define TPAGER_LCD_VERTICAL_RESOLUTION 480
|
||||||
|
#define TPAGER_LCD_SPI_TRANSFER_HEIGHT (TPAGER_LCD_VERTICAL_RESOLUTION / 10)
|
||||||
359
Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp
Normal file
359
Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
#include "TpagerKeyboard.h"
|
||||||
|
#include <Tactility/hal/i2c/I2c.h>
|
||||||
|
#include <driver/i2c.h>
|
||||||
|
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
|
||||||
|
#include <Tactility/Log.h>
|
||||||
|
|
||||||
|
#define TAG "tpager_keyboard"
|
||||||
|
|
||||||
|
#define ENCODER_A GPIO_NUM_40
|
||||||
|
#define ENCODER_B GPIO_NUM_41
|
||||||
|
#define ENCODER_ENTER GPIO_NUM_7
|
||||||
|
#define BACKLIGHT GPIO_NUM_46
|
||||||
|
|
||||||
|
#define KB_ROWS 4
|
||||||
|
#define KB_COLS 11
|
||||||
|
|
||||||
|
// Lowercase Keymap
|
||||||
|
static constexpr char keymap_lc[KB_ROWS][KB_COLS] = {
|
||||||
|
{'\0', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'},
|
||||||
|
{'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '\n', '\0'},
|
||||||
|
{'z', 'x', 'c', 'v', 'b', 'n', 'm', '\0', LV_KEY_BACKSPACE, ' ', '\0'},
|
||||||
|
{'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Uppercase Keymap
|
||||||
|
static constexpr char keymap_uc[KB_ROWS][KB_COLS] = {
|
||||||
|
{'\0', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'},
|
||||||
|
{'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', '\n', '\0'},
|
||||||
|
{'Z', 'X', 'C', 'V', 'B', 'N', 'M', '\0', LV_KEY_BACKSPACE, ' ', '\0'},
|
||||||
|
{'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Symbol Keymap
|
||||||
|
static constexpr char keymap_sy[KB_ROWS][KB_COLS] = {
|
||||||
|
{'\0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'},
|
||||||
|
{'.', '/', '+', '-', '=', ':', '\'', '"', '@', '\t', '\0'},
|
||||||
|
{'_', '$', ';', '?', '!', ',', '.', '\0', LV_KEY_BACKSPACE, ' ', '\0'},
|
||||||
|
{'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'}
|
||||||
|
};
|
||||||
|
|
||||||
|
static QueueHandle_t keyboardMsg;
|
||||||
|
|
||||||
|
static void keyboard_read_callback(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||||
|
TpagerKeyboard* kb = (TpagerKeyboard*)lv_indev_get_user_data(indev);
|
||||||
|
static bool enter_prev = false;
|
||||||
|
char keypress = 0;
|
||||||
|
|
||||||
|
// Defaults
|
||||||
|
data->key = 0;
|
||||||
|
data->state = LV_INDEV_STATE_RELEASED;
|
||||||
|
|
||||||
|
if (xQueueReceive(keyboardMsg, &keypress, pdMS_TO_TICKS(50)) == pdPASS) {
|
||||||
|
data->key = keypress;
|
||||||
|
data->state = LV_INDEV_STATE_PRESSED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void encoder_read_callback(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||||
|
TpagerKeyboard* kb = (TpagerKeyboard*)lv_indev_get_user_data(indev);
|
||||||
|
const int enter_filter_threshold = 2;
|
||||||
|
static int enter_filter = 0;
|
||||||
|
const int pulses_click = 4;
|
||||||
|
static int pulses_prev = 0;
|
||||||
|
bool anyinput = false;
|
||||||
|
|
||||||
|
// Defaults
|
||||||
|
data->enc_diff = 0;
|
||||||
|
data->state = LV_INDEV_STATE_RELEASED;
|
||||||
|
|
||||||
|
int pulses = kb->getEncoderPulses();
|
||||||
|
int pulse_diff = (pulses - pulses_prev);
|
||||||
|
if ((pulse_diff > pulses_click) || (pulse_diff < -pulses_click)) {
|
||||||
|
data->enc_diff = pulse_diff / pulses_click;
|
||||||
|
pulses_prev = pulses;
|
||||||
|
anyinput = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enter = !gpio_get_level(ENCODER_ENTER);
|
||||||
|
if (enter && (enter_filter < enter_filter_threshold)) {
|
||||||
|
enter_filter++;
|
||||||
|
}
|
||||||
|
if (!enter && (enter_filter > 0)) {
|
||||||
|
enter_filter--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enter_filter == enter_filter_threshold) {
|
||||||
|
data->state = LV_INDEV_STATE_PRESSED;
|
||||||
|
anyinput = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anyinput) {
|
||||||
|
kb->makeBacklightImpulse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TpagerKeyboard::processKeyboard() {
|
||||||
|
static bool shift_pressed = false;
|
||||||
|
static bool sym_pressed = false;
|
||||||
|
static bool cap_toggle = false;
|
||||||
|
static bool cap_toggle_armed = true;
|
||||||
|
bool anykey_pressed = false;
|
||||||
|
|
||||||
|
if (keypad->update()) {
|
||||||
|
anykey_pressed = (keypad->pressed_key_count > 0);
|
||||||
|
for (int i = 0; i < keypad->pressed_key_count; i++) {
|
||||||
|
auto row = keypad->pressed_list[i].row;
|
||||||
|
auto col = keypad->pressed_list[i].col;
|
||||||
|
auto hold = keypad->pressed_list[i].hold_time;
|
||||||
|
|
||||||
|
if ((row == 1) && (col == 10)) {
|
||||||
|
sym_pressed = true;
|
||||||
|
}
|
||||||
|
if ((row == 2) && (col == 7)) {
|
||||||
|
shift_pressed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((sym_pressed && shift_pressed) && cap_toggle_armed) {
|
||||||
|
cap_toggle = !cap_toggle;
|
||||||
|
cap_toggle_armed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < keypad->pressed_key_count; i++) {
|
||||||
|
auto row = keypad->pressed_list[i].row;
|
||||||
|
auto col = keypad->pressed_list[i].col;
|
||||||
|
auto hold = keypad->pressed_list[i].hold_time;
|
||||||
|
char chr = '\0';
|
||||||
|
if (sym_pressed) {
|
||||||
|
chr = keymap_sy[row][col];
|
||||||
|
} else if (shift_pressed || cap_toggle) {
|
||||||
|
chr = keymap_uc[row][col];
|
||||||
|
} else {
|
||||||
|
chr = keymap_lc[row][col];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chr != '\0') xQueueSend(keyboardMsg, (void*)&chr, portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < keypad->released_key_count; i++) {
|
||||||
|
auto row = keypad->released_list[i].row;
|
||||||
|
auto col = keypad->released_list[i].col;
|
||||||
|
|
||||||
|
if ((row == 1) && (col == 10)) {
|
||||||
|
sym_pressed = false;
|
||||||
|
}
|
||||||
|
if ((row == 2) && (col == 7)) {
|
||||||
|
shift_pressed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!sym_pressed && !shift_pressed) && !cap_toggle_armed) {
|
||||||
|
cap_toggle_armed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anykey_pressed) {
|
||||||
|
makeBacklightImpulse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TpagerKeyboard::start(lv_display_t* display) {
|
||||||
|
backlightOkay = initBacklight(BACKLIGHT, 30000, LEDC_TIMER_0, LEDC_CHANNEL_1);
|
||||||
|
initEncoder();
|
||||||
|
keypad->init(KB_ROWS, KB_COLS);
|
||||||
|
gpio_input_enable(ENCODER_ENTER);
|
||||||
|
|
||||||
|
assert(inputTimer == nullptr);
|
||||||
|
inputTimer = std::make_unique<tt::Timer>(tt::Timer::Type::Periodic, [this] {
|
||||||
|
processKeyboard();
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(backlightImpulseTimer == nullptr);
|
||||||
|
backlightImpulseTimer = std::make_unique<tt::Timer>(tt::Timer::Type::Periodic, [this] {
|
||||||
|
processBacklightImpuse();
|
||||||
|
});
|
||||||
|
|
||||||
|
kbHandle = lv_indev_create();
|
||||||
|
lv_indev_set_type(kbHandle, LV_INDEV_TYPE_KEYPAD);
|
||||||
|
lv_indev_set_read_cb(kbHandle, &keyboard_read_callback);
|
||||||
|
lv_indev_set_display(kbHandle, display);
|
||||||
|
lv_indev_set_user_data(kbHandle, this);
|
||||||
|
|
||||||
|
encHandle = lv_indev_create();
|
||||||
|
lv_indev_set_type(encHandle, LV_INDEV_TYPE_ENCODER);
|
||||||
|
lv_indev_set_read_cb(encHandle, &encoder_read_callback);
|
||||||
|
lv_indev_set_display(encHandle, display);
|
||||||
|
lv_indev_set_user_data(encHandle, this);
|
||||||
|
|
||||||
|
inputTimer->start(20 / portTICK_PERIOD_MS);
|
||||||
|
backlightImpulseTimer->start(50 / portTICK_PERIOD_MS);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TpagerKeyboard::stop() {
|
||||||
|
assert(inputTimer);
|
||||||
|
inputTimer->stop();
|
||||||
|
inputTimer = nullptr;
|
||||||
|
|
||||||
|
assert(backlightImpulseTimer);
|
||||||
|
backlightImpulseTimer->stop();
|
||||||
|
backlightImpulseTimer = nullptr;
|
||||||
|
|
||||||
|
lv_indev_delete(kbHandle);
|
||||||
|
kbHandle = nullptr;
|
||||||
|
lv_indev_delete(encHandle);
|
||||||
|
encHandle = nullptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TpagerKeyboard::isAttached() const {
|
||||||
|
return tt::hal::i2c::masterHasDeviceAtAddress(keypad->getPort(), keypad->getAddress(), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TpagerKeyboard::initEncoder(void) {
|
||||||
|
const int low_limit = -127;
|
||||||
|
const int high_limit = 126;
|
||||||
|
|
||||||
|
// Accum. count makes it that over- and underflows are automatically compensated.
|
||||||
|
// Prerequisite: watchpoints at low and high limit
|
||||||
|
pcnt_unit_config_t unit_config = {
|
||||||
|
.low_limit = low_limit,
|
||||||
|
.high_limit = high_limit,
|
||||||
|
.flags = {.accum_count = 1},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pcnt_new_unit(&unit_config, &encPcntUnit) != ESP_OK) {
|
||||||
|
TT_LOG_E(TAG, "Pulsecounter intialization failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
pcnt_glitch_filter_config_t filter_config = {
|
||||||
|
.max_glitch_ns = 5000,
|
||||||
|
};
|
||||||
|
if (pcnt_unit_set_glitch_filter(encPcntUnit, &filter_config) != ESP_OK) {
|
||||||
|
TT_LOG_E(TAG, "Pulsecounter glitch filter config failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
pcnt_chan_config_t chan_1_config = {
|
||||||
|
.edge_gpio_num = ENCODER_A,
|
||||||
|
.level_gpio_num = ENCODER_B,
|
||||||
|
};
|
||||||
|
pcnt_chan_config_t chan_2_config = {
|
||||||
|
.edge_gpio_num = ENCODER_B,
|
||||||
|
.level_gpio_num = ENCODER_A,
|
||||||
|
};
|
||||||
|
|
||||||
|
pcnt_channel_handle_t pcnt_chan_1 = NULL;
|
||||||
|
pcnt_channel_handle_t pcnt_chan_2 = NULL;
|
||||||
|
|
||||||
|
if ((pcnt_new_channel(encPcntUnit, &chan_1_config, &pcnt_chan_1) != ESP_OK) ||
|
||||||
|
(pcnt_new_channel(encPcntUnit, &chan_2_config, &pcnt_chan_2) != ESP_OK)) {
|
||||||
|
TT_LOG_E(TAG, "Pulsecounter channel config failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// second argument is rising edge, third argument is falling edge
|
||||||
|
if ((pcnt_channel_set_edge_action(pcnt_chan_1, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE) != ESP_OK) ||
|
||||||
|
(pcnt_channel_set_edge_action(pcnt_chan_2, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_DECREASE) != ESP_OK)) {
|
||||||
|
TT_LOG_E(TAG, "Pulsecounter edge action config failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// second argument is low level, third argument is high level
|
||||||
|
if ((pcnt_channel_set_level_action(pcnt_chan_1, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE) != ESP_OK) ||
|
||||||
|
(pcnt_channel_set_level_action(pcnt_chan_2, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE) != ESP_OK)) {
|
||||||
|
TT_LOG_E(TAG, "Pulsecounter level action config failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((pcnt_unit_add_watch_point(encPcntUnit, low_limit) != ESP_OK) ||
|
||||||
|
(pcnt_unit_add_watch_point(encPcntUnit, high_limit) != ESP_OK)) {
|
||||||
|
TT_LOG_E(TAG, "Pulsecounter watch point config failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pcnt_unit_enable(encPcntUnit) != ESP_OK) {
|
||||||
|
TT_LOG_E(TAG, "Pulsecounter could not be enabled");
|
||||||
|
}
|
||||||
|
if (pcnt_unit_clear_count(encPcntUnit) != ESP_OK) {
|
||||||
|
TT_LOG_E(TAG, "Pulsecounter could not be cleared");
|
||||||
|
}
|
||||||
|
if (pcnt_unit_start(encPcntUnit) != ESP_OK) {
|
||||||
|
TT_LOG_E(TAG, "Pulsecounter could not be started");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TpagerKeyboard::getEncoderPulses() {
|
||||||
|
int pulses = 0;
|
||||||
|
pcnt_unit_get_count(encPcntUnit, &pulses);
|
||||||
|
return pulses;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool TpagerKeyboard::initBacklight(gpio_num_t pin, uint32_t frequencyHz, ledc_timer_t timer, ledc_channel_t channel) {
|
||||||
|
backlightPin = pin;
|
||||||
|
backlightTimer = timer;
|
||||||
|
backlightChannel = channel;
|
||||||
|
|
||||||
|
ledc_timer_config_t ledc_timer = {
|
||||||
|
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||||||
|
.duty_resolution = LEDC_TIMER_8_BIT,
|
||||||
|
.timer_num = backlightTimer,
|
||||||
|
.freq_hz = frequencyHz,
|
||||||
|
.clk_cfg = LEDC_AUTO_CLK,
|
||||||
|
.deconfigure = false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ledc_timer_config(&ledc_timer) != ESP_OK) {
|
||||||
|
TT_LOG_E(TAG, "Backlight timer config failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ledc_channel_config_t ledc_channel = {
|
||||||
|
.gpio_num = backlightPin,
|
||||||
|
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||||||
|
.channel = backlightChannel,
|
||||||
|
.intr_type = LEDC_INTR_DISABLE,
|
||||||
|
.timer_sel = backlightTimer,
|
||||||
|
.duty = 0,
|
||||||
|
.hpoint = 0,
|
||||||
|
.sleep_mode = LEDC_SLEEP_MODE_NO_ALIVE_NO_PD,
|
||||||
|
.flags = {
|
||||||
|
.output_invert = 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ledc_channel_config(&ledc_channel) != ESP_OK) {
|
||||||
|
TT_LOG_E(TAG, "Backlight channel config failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TpagerKeyboard::setBacklightDuty(uint8_t duty) {
|
||||||
|
if (!backlightOkay) {
|
||||||
|
TT_LOG_E(TAG, "Backlight not ready");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (ledc_set_duty(LEDC_LOW_SPEED_MODE, backlightChannel, duty) == ESP_OK) &&
|
||||||
|
(ledc_update_duty(LEDC_LOW_SPEED_MODE, backlightChannel) == ESP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TpagerKeyboard::makeBacklightImpulse() {
|
||||||
|
backlightImpulseDuty = 255;
|
||||||
|
setBacklightDuty(backlightImpulseDuty);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TpagerKeyboard::processBacklightImpuse() {
|
||||||
|
if (backlightImpulseDuty > 64) {
|
||||||
|
backlightImpulseDuty--;
|
||||||
|
setBacklightDuty(backlightImpulseDuty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern std::shared_ptr<Tca8418> tca8418;
|
||||||
|
std::shared_ptr<tt::hal::keyboard::KeyboardDevice> createKeyboard() {
|
||||||
|
keyboardMsg = xQueueCreate(20, sizeof(char));
|
||||||
|
|
||||||
|
return std::make_shared<TpagerKeyboard>(tca8418);
|
||||||
|
}
|
||||||
54
Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.h
Normal file
54
Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.h
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Tactility/TactilityCore.h>
|
||||||
|
#include <Tactility/hal/keyboard/KeyboardDevice.h>
|
||||||
|
|
||||||
|
#include <Tca8418.h>
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <driver/ledc.h>
|
||||||
|
#include <driver/pulse_cnt.h>
|
||||||
|
|
||||||
|
#include <Tactility/Timer.h>
|
||||||
|
|
||||||
|
|
||||||
|
class TpagerKeyboard : public tt::hal::keyboard::KeyboardDevice {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
lv_indev_t* _Nullable kbHandle = nullptr;
|
||||||
|
lv_indev_t* _Nullable encHandle = nullptr;
|
||||||
|
pcnt_unit_handle_t encPcntUnit = nullptr;
|
||||||
|
gpio_num_t backlightPin = GPIO_NUM_NC;
|
||||||
|
ledc_timer_t backlightTimer;
|
||||||
|
ledc_channel_t backlightChannel;
|
||||||
|
bool backlightOkay = false;
|
||||||
|
int backlightImpulseDuty = 0;
|
||||||
|
|
||||||
|
std::shared_ptr<Tca8418> keypad;
|
||||||
|
std::unique_ptr<tt::Timer> inputTimer;
|
||||||
|
std::unique_ptr<tt::Timer> backlightImpulseTimer;
|
||||||
|
|
||||||
|
void initEncoder(void);
|
||||||
|
bool initBacklight(gpio_num_t pin, uint32_t frequencyHz, ledc_timer_t timer, ledc_channel_t channel);
|
||||||
|
void processKeyboard();
|
||||||
|
void processBacklightImpuse();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
TpagerKeyboard(std::shared_ptr<Tca8418> tca) : keypad(std::move(tca)) {}
|
||||||
|
~TpagerKeyboard() {}
|
||||||
|
|
||||||
|
std::string getName() const final { return "T-Lora Pager Keyboard"; }
|
||||||
|
std::string getDescription() const final { return "I2C keyboard with encoder"; }
|
||||||
|
|
||||||
|
bool start(lv_display_t* display) override;
|
||||||
|
bool stop() override;
|
||||||
|
bool isAttached() const override;
|
||||||
|
lv_indev_t* _Nullable getLvglIndev() override { return kbHandle; }
|
||||||
|
|
||||||
|
int getEncoderPulses();
|
||||||
|
bool setBacklightDuty(uint8_t duty);
|
||||||
|
void makeBacklightImpulse();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<tt::hal::keyboard::KeyboardDevice> createKeyboard();
|
||||||
90
Boards/LilygoTLoraPager/Source/hal/TpagerPower.cpp
Normal file
90
Boards/LilygoTLoraPager/Source/hal/TpagerPower.cpp
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
#include "TpagerPower.h"
|
||||||
|
|
||||||
|
#include <Tactility/Log.h>
|
||||||
|
|
||||||
|
#define TAG "power"
|
||||||
|
|
||||||
|
#define TPAGER_GAUGE_I2C_BUS_HANDLE I2C_NUM_0
|
||||||
|
|
||||||
|
/*
|
||||||
|
TpagerPower::TpagerPower() : gauge(TPAGER_GAUGE_I2C_BUS_HANDLE) {
|
||||||
|
gauge->configureCapacity(1500, 1500);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
TpagerPower::~TpagerPower() {}
|
||||||
|
|
||||||
|
bool TpagerPower::supportsMetric(MetricType type) const {
|
||||||
|
switch (type) {
|
||||||
|
using enum MetricType;
|
||||||
|
case IsCharging:
|
||||||
|
case Current:
|
||||||
|
case BatteryVoltage:
|
||||||
|
case ChargeLevel:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // Safety guard for when new enum values are introduced
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TpagerPower::getMetric(MetricType type, MetricData& data) {
|
||||||
|
/* IsCharging, // bool
|
||||||
|
Current, // int32_t, mAh - battery current: either during charging (positive value) or discharging (negative value)
|
||||||
|
BatteryVoltage, // uint32_t, mV
|
||||||
|
ChargeLevel, // uint8_t [0, 100]
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint16_t u16 = 0;
|
||||||
|
int16_t s16 = 0;
|
||||||
|
switch (type) {
|
||||||
|
using enum MetricType;
|
||||||
|
case IsCharging:
|
||||||
|
Bq27220::BatteryStatus status;
|
||||||
|
if (gauge->getBatteryStatus(status)) {
|
||||||
|
data.valueAsBool = !status.reg.DSG;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case Current:
|
||||||
|
if (gauge->getCurrent(s16)) {
|
||||||
|
data.valueAsInt32 = s16;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BatteryVoltage:
|
||||||
|
if (gauge->getVoltage(u16)) {
|
||||||
|
data.valueAsUint32 = u16;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ChargeLevel:
|
||||||
|
if (gauge->getStateOfCharge(u16)) {
|
||||||
|
data.valueAsUint8 = u16;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // Safety guard for when new enum values are introduced
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::shared_ptr<PowerDevice> power;
|
||||||
|
extern std::shared_ptr<Bq27220> bq27220;
|
||||||
|
|
||||||
|
std::shared_ptr<PowerDevice> tpager_get_power() {
|
||||||
|
if (power == nullptr) {
|
||||||
|
power = std::make_shared<TpagerPower>(bq27220);
|
||||||
|
}
|
||||||
|
return power;
|
||||||
|
}
|
||||||
26
Boards/LilygoTLoraPager/Source/hal/TpagerPower.h
Normal file
26
Boards/LilygoTLoraPager/Source/hal/TpagerPower.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Tactility/hal/power/PowerDevice.h"
|
||||||
|
#include <Bq27220.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
using tt::hal::power::PowerDevice;
|
||||||
|
|
||||||
|
class TpagerPower : public PowerDevice {
|
||||||
|
std::shared_ptr<Bq27220> gauge;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
TpagerPower(std::shared_ptr<Bq27220> bq) : gauge(std::move(bq)) {}
|
||||||
|
~TpagerPower();
|
||||||
|
|
||||||
|
std::string getName() const final { return "T-LoRa Pager Power measument"; }
|
||||||
|
std::string getDescription() const final { return "Power measurement interface via I2C fuel gauge"; }
|
||||||
|
|
||||||
|
bool supportsMetric(MetricType type) const override;
|
||||||
|
bool getMetric(MetricType type, MetricData& data) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<PowerDevice> tpager_get_power();
|
||||||
31
Boards/LilygoTLoraPager/Source/hal/TpagerSdCard.cpp
Normal file
31
Boards/LilygoTLoraPager/Source/hal/TpagerSdCard.cpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#include "TpagerSdCard.h"
|
||||||
|
|
||||||
|
#include <Tactility/hal/sdcard/SpiSdCardDevice.h>
|
||||||
|
#include <Tactility/lvgl/LvglSync.h>
|
||||||
|
|
||||||
|
#include <esp_vfs_fat.h>
|
||||||
|
|
||||||
|
using tt::hal::sdcard::SpiSdCardDevice;
|
||||||
|
|
||||||
|
#define TPAGER_SDCARD_PIN_CS GPIO_NUM_21
|
||||||
|
#define TPAGER_LCD_PIN_CS GPIO_NUM_38
|
||||||
|
#define TPAGER_RADIO_PIN_CS GPIO_NUM_36
|
||||||
|
|
||||||
|
std::shared_ptr<SdCardDevice> createTpagerSdCard() {
|
||||||
|
auto* configuration = new SpiSdCardDevice::Config(
|
||||||
|
TPAGER_SDCARD_PIN_CS,
|
||||||
|
GPIO_NUM_NC,
|
||||||
|
GPIO_NUM_NC,
|
||||||
|
GPIO_NUM_NC,
|
||||||
|
SdCardDevice::MountBehaviour::AtBoot,
|
||||||
|
tt::lvgl::getSyncLock(),
|
||||||
|
{TPAGER_RADIO_PIN_CS,
|
||||||
|
TPAGER_LCD_PIN_CS}
|
||||||
|
);
|
||||||
|
|
||||||
|
auto* sdcard = (SdCardDevice*)new SpiSdCardDevice(
|
||||||
|
std::unique_ptr<SpiSdCardDevice::Config>(configuration)
|
||||||
|
);
|
||||||
|
|
||||||
|
return std::shared_ptr<SdCardDevice>(sdcard);
|
||||||
|
}
|
||||||
7
Boards/LilygoTLoraPager/Source/hal/TpagerSdCard.h
Normal file
7
Boards/LilygoTLoraPager/Source/hal/TpagerSdCard.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Tactility/hal/sdcard/SdCardDevice.h"
|
||||||
|
|
||||||
|
using tt::hal::sdcard::SdCardDevice;
|
||||||
|
|
||||||
|
std::shared_ptr<SdCardDevice> createTpagerSdCard();
|
||||||
@ -47,6 +47,8 @@ function(INIT_TACTILITY_GLOBALS SDKCONFIG_FILE)
|
|||||||
set(TACTILITY_BOARD_PROJECT ElecrowCrowpanelBasic50)
|
set(TACTILITY_BOARD_PROJECT ElecrowCrowpanelBasic50)
|
||||||
elseif (board_id STREQUAL "lilygo-tdeck")
|
elseif (board_id STREQUAL "lilygo-tdeck")
|
||||||
set(TACTILITY_BOARD_PROJECT LilygoTdeck)
|
set(TACTILITY_BOARD_PROJECT LilygoTdeck)
|
||||||
|
elseif (board_id STREQUAL "lilygo-tlora-pager")
|
||||||
|
set(TACTILITY_BOARD_PROJECT LilygoTLoraPager)
|
||||||
elseif (board_id STREQUAL "m5stack-core2")
|
elseif (board_id STREQUAL "m5stack-core2")
|
||||||
set(TACTILITY_BOARD_PROJECT M5stackCore2)
|
set(TACTILITY_BOARD_PROJECT M5stackCore2)
|
||||||
elseif (board_id STREQUAL "m5stack-cores3")
|
elseif (board_id STREQUAL "m5stack-cores3")
|
||||||
|
|||||||
@ -53,6 +53,12 @@ Website: https://github.com/meshtastic/firmware
|
|||||||
|
|
||||||
License: [GPL v3.0](https://github.com/meshtastic/firmware/blob/master/LICENSE)
|
License: [GPL v3.0](https://github.com/meshtastic/firmware/blob/master/LICENSE)
|
||||||
|
|
||||||
|
### BQ27220 Driver
|
||||||
|
|
||||||
|
Website: https://github.com/Xinyuan-LilyGO/T-Echo/blob/main/LICENSE
|
||||||
|
|
||||||
|
License: [MIT](https://github.com/Xinyuan-LilyGO/T-Echo/blob/main/LICENSE)
|
||||||
|
|
||||||
### Other Components
|
### Other Components
|
||||||
|
|
||||||
See `/components` for the respective projects and their licenses.
|
See `/components` for the respective projects and their licenses.
|
||||||
|
|||||||
@ -8,10 +8,10 @@
|
|||||||
* https://gitlab.com/hamishcunningham/unphonelibrary/-/blob/main/unPhone.h?ref_type=heads
|
* https://gitlab.com/hamishcunningham/unphonelibrary/-/blob/main/unPhone.h?ref_type=heads
|
||||||
*/
|
*/
|
||||||
namespace registers {
|
namespace registers {
|
||||||
static const uint8_t CHARGE_TERMINATION = 0x05U; // Datasheet page 35: Charge end/timer cntrl
|
static const uint8_t CHARGE_TERMINATION = 0x05U; // Datasheet page 35: Charge end/timer cntrl
|
||||||
static const uint8_t OPERATION_CONTROL = 0x07U; // Datasheet page 37: Misc operation control
|
static const uint8_t OPERATION_CONTROL = 0x07U; // Datasheet page 37: Misc operation control
|
||||||
static const uint8_t STATUS = 0x08U; // Datasheet page 38: System status
|
static const uint8_t STATUS = 0x08U; // Datasheet page 38: System status
|
||||||
static const uint8_t VERSION = 0x0AU; // Datasheet page 38: Vendor/part/revision status
|
static const uint8_t VERSION = 0x0AU; // Datasheet page 38: Vendor/part/revision status
|
||||||
} // namespace registers
|
} // namespace registers
|
||||||
|
|
||||||
bool Bq24295::readChargeTermination(uint8_t& out) const {
|
bool Bq24295::readChargeTermination(uint8_t& out) const {
|
||||||
|
|||||||
5
Drivers/BQ27220/CMakeLists.txt
Normal file
5
Drivers/BQ27220/CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRC_DIRS "Source"
|
||||||
|
INCLUDE_DIRS "Source"
|
||||||
|
REQUIRES Tactility
|
||||||
|
)
|
||||||
6
Drivers/BQ27220/README.md
Normal file
6
Drivers/BQ27220/README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# BQ27220
|
||||||
|
|
||||||
|
Power management: Single-Cell CEDV Fuel Gauge
|
||||||
|
|
||||||
|
[Datasheet](https://www.ti.com/lit/gpn/bq27220)
|
||||||
|
[User Guide](https://www.ti.com/lit/pdf/sluubd4)
|
||||||
370
Drivers/BQ27220/Source/Bq27220.cpp
Normal file
370
Drivers/BQ27220/Source/Bq27220.cpp
Normal file
@ -0,0 +1,370 @@
|
|||||||
|
#include "Bq27220.h"
|
||||||
|
#include <Tactility/Log.h>
|
||||||
|
|
||||||
|
#include "esp_sleep.h"
|
||||||
|
|
||||||
|
#define TAG "bq27220"
|
||||||
|
|
||||||
|
#define ARRAYSIZE(a) (sizeof(a) / sizeof(*(a)))
|
||||||
|
|
||||||
|
static uint8_t highByte(const uint16_t word) { return (word >> 8) & 0xFF; }
|
||||||
|
static uint8_t lowByte(const uint16_t word) { return word & 0xFF; }
|
||||||
|
static constexpr void swapEndianess(uint16_t &word) { word = (lowByte(word) << 8) | highByte(word); }
|
||||||
|
|
||||||
|
namespace registers {
|
||||||
|
static const uint16_t SUBCMD_CTRL_STATUS = 0x0000U;
|
||||||
|
static const uint16_t SUBCMD_DEVICE_NUMBER = 0x0001U;
|
||||||
|
static const uint16_t SUBCMD_FW_VERSION = 0x0002U;
|
||||||
|
static const uint16_t SUBCMD_HW_VERSION = 0x0003U;
|
||||||
|
static const uint16_t SUBCMD_BOARD_OFFSET = 0x0009U;
|
||||||
|
static const uint16_t SUBCMD_CC_OFFSET = 0x000AU;
|
||||||
|
static const uint16_t SUBCMD_CC_OFFSET_SAVE = 0x000BU;
|
||||||
|
static const uint16_t SUBCMD_OCV_CMD = 0x000CU;
|
||||||
|
static const uint16_t SUBCMD_BAT_INSERT = 0x000DU;
|
||||||
|
static const uint16_t SUBCMD_BAT_REMOVE = 0x000EU;
|
||||||
|
static const uint16_t SUBCMD_SET_SNOOZE = 0x0013U;
|
||||||
|
static const uint16_t SUBCMD_CLEAR_SNOOZE = 0x0014U;
|
||||||
|
static const uint16_t SUBCMD_SET_PROFILE_1 = 0x0015U;
|
||||||
|
static const uint16_t SUBCMD_SET_PROFILE_2 = 0x0016U;
|
||||||
|
static const uint16_t SUBCMD_SET_PROFILE_3 = 0x0017U;
|
||||||
|
static const uint16_t SUBCMD_SET_PROFILE_4 = 0x0018U;
|
||||||
|
static const uint16_t SUBCMD_SET_PROFILE_5 = 0x0019U;
|
||||||
|
static const uint16_t SUBCMD_SET_PROFILE_6 = 0x001AU;
|
||||||
|
static const uint16_t SUBCMD_CAL_TOGGLE = 0x002DU;
|
||||||
|
static const uint16_t SUBCMD_SEALED = 0x0030U;
|
||||||
|
static const uint16_t SUBCMD_RESET = 0x0041U;
|
||||||
|
static const uint16_t SUBCMD_EXIT_CAL = 0x0080U;
|
||||||
|
static const uint16_t SUBCMD_ENTER_CAL = 0x0081U;
|
||||||
|
static const uint16_t SUBCMD_ENTER_CFG_UPDATE = 0x0090U;
|
||||||
|
static const uint16_t SUBCMD_EXIT_CFG_UPDATE_REINIT = 0x0091U;
|
||||||
|
static const uint16_t SUBCMD_EXIT_CFG_UPDATE = 0x0092U;
|
||||||
|
static const uint16_t SUBCMD_RETURN_TO_ROM = 0x0F00U;
|
||||||
|
|
||||||
|
static const uint8_t CMD_CONTROL = 0x00U;
|
||||||
|
static const uint8_t CMD_AT_RATE = 0x02U;
|
||||||
|
static const uint8_t CMD_AT_RATE_TIME_TO_EMPTY = 0x04U;
|
||||||
|
static const uint8_t CMD_TEMPERATURE = 0x06U;
|
||||||
|
static const uint8_t CMD_VOLTAGE = 0x08U;
|
||||||
|
static const uint8_t CMD_BATTERY_STATUS = 0x0AU;
|
||||||
|
static const uint8_t CMD_CURRENT = 0x0CU;
|
||||||
|
static const uint8_t CMD_REMAINING_CAPACITY = 0x10U;
|
||||||
|
static const uint8_t CMD_FULL_CHARGE_CAPACITY = 0x12U;
|
||||||
|
static const uint8_t CMD_AVG_CURRENT = 0x14U;
|
||||||
|
static const uint8_t CMD_TIME_TO_EMPTY = 0x16U;
|
||||||
|
static const uint8_t CMD_TIME_TO_FULL = 0x18U;
|
||||||
|
static const uint8_t CMD_STANDBY_CURRENT = 0x1AU;
|
||||||
|
static const uint8_t CMD_STANDBY_TIME_TO_EMPTY = 0x1CU;
|
||||||
|
static const uint8_t CMD_MAX_LOAD_CURRENT = 0x1EU;
|
||||||
|
static const uint8_t CMD_MAX_LOAD_TIME_TO_EMPTY = 0x20U;
|
||||||
|
static const uint8_t CMD_RAW_COULOMB_COUNT = 0x22U;
|
||||||
|
static const uint8_t CMD_AVG_POWER = 0x24U;
|
||||||
|
static const uint8_t CMD_INTERNAL_TEMPERATURE = 0x28U;
|
||||||
|
static const uint8_t CMD_CYCLE_COUNT = 0x2AU;
|
||||||
|
static const uint8_t CMD_STATE_OF_CHARGE = 0x2CU;
|
||||||
|
static const uint8_t CMD_STATE_OF_HEALTH = 0x2EU;
|
||||||
|
static const uint8_t CMD_CHARGE_VOLTAGE = 0x30U;
|
||||||
|
static const uint8_t CMD_CHARGE_CURRENT = 0x32U;
|
||||||
|
static const uint8_t CMD_BTP_DISCHARGE_SET = 0x34U;
|
||||||
|
static const uint8_t CMD_BTP_CHARGE_SET = 0x36U;
|
||||||
|
static const uint8_t CMD_OPERATION_STATUS = 0x3AU;
|
||||||
|
static const uint8_t CMD_DESIGN_CAPACITY = 0x3CU;
|
||||||
|
static const uint8_t CMD_SELECT_SUBCLASS = 0x3EU;
|
||||||
|
static const uint8_t CMD_MAC_DATA = 0x40U;
|
||||||
|
static const uint8_t CMD_MAC_DATA_SUM = 0x60U;
|
||||||
|
static const uint8_t CMD_MAC_DATA_LEN = 0x61U;
|
||||||
|
static const uint8_t CMD_ANALOG_COUNT = 0x79U;
|
||||||
|
static const uint8_t CMD_RAW_CURRENT = 0x7AU;
|
||||||
|
static const uint8_t CMD_RAW_VOLTAGE = 0x7CU;
|
||||||
|
static const uint8_t CMD_RAW_INTERNAL_TEMPERATURE = 0x7EU;
|
||||||
|
static const uint8_t MAC_BUFFER_START = 0x40U;
|
||||||
|
static const uint8_t MAC_BUFFER_END = 0x5FU;
|
||||||
|
static const uint8_t MAC_DATA_SUM = 0x60U;
|
||||||
|
static const uint8_t MAC_DATA_LEN = 0x61U;
|
||||||
|
static const uint8_t ROM_START = 0x3EU;
|
||||||
|
|
||||||
|
static const uint16_t ROM_FULL_CHARGE_CAPACITY = 0x929DU;
|
||||||
|
static const uint16_t ROM_DESIGN_CAPACITY = 0x929FU;
|
||||||
|
static const uint16_t ROM_OPERATION_CONFIG_A = 0x9206U;
|
||||||
|
static const uint16_t ROM_OPERATION_CONFIG_B = 0x9208U;
|
||||||
|
|
||||||
|
} // namespace registers
|
||||||
|
|
||||||
|
bool Bq27220::configureCapacity(uint16_t designCapacity, uint16_t fullChargeCapacity) {
|
||||||
|
return performConfigUpdate([this, designCapacity, fullChargeCapacity]() {
|
||||||
|
// Set the design capacity
|
||||||
|
if (!writeConfig16(registers::ROM_DESIGN_CAPACITY, designCapacity)) {
|
||||||
|
TT_LOG_E(TAG, "Failed to set design capacity!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||||
|
|
||||||
|
// Set full charge capacity
|
||||||
|
if (!writeConfig16(registers::ROM_FULL_CHARGE_CAPACITY, fullChargeCapacity)) {
|
||||||
|
TT_LOG_E(TAG, "Failed to set full charge capacity!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::getVoltage(uint16_t &value) {
|
||||||
|
if (readRegister16(registers::CMD_VOLTAGE, value)) {
|
||||||
|
swapEndianess(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::getCurrent(int16_t &value) {
|
||||||
|
uint16_t u16 = 0;
|
||||||
|
if (readRegister16(registers::CMD_CURRENT, u16)) {
|
||||||
|
swapEndianess(u16);
|
||||||
|
value = (int16_t)u16;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::getBatteryStatus(Bq27220::BatteryStatus &batt_sta) {
|
||||||
|
if (readRegister16(registers::CMD_BATTERY_STATUS, batt_sta.full)) {
|
||||||
|
swapEndianess(batt_sta.full);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::getOperationStatus(OperationStatus &oper_sta) {
|
||||||
|
if (readRegister16(registers::CMD_OPERATION_STATUS, oper_sta.full)) {
|
||||||
|
swapEndianess(oper_sta.full);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::getTemperature(uint16_t &value) {
|
||||||
|
if (readRegister16(registers::CMD_TEMPERATURE, value)) {
|
||||||
|
swapEndianess(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::getFullChargeCapacity(uint16_t &value) {
|
||||||
|
if (readRegister16(registers::CMD_FULL_CHARGE_CAPACITY, value)) {
|
||||||
|
swapEndianess(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::getDesignCapacity(uint16_t &value) {
|
||||||
|
if (readRegister16(registers::CMD_DESIGN_CAPACITY, value)) {
|
||||||
|
swapEndianess(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::getRemainingCapacity(uint16_t &value) {
|
||||||
|
if (readRegister16(registers::CMD_REMAINING_CAPACITY, value)) {
|
||||||
|
swapEndianess(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::getStateOfCharge(uint16_t &value) {
|
||||||
|
if (readRegister16(registers::CMD_STATE_OF_CHARGE, value)) {
|
||||||
|
swapEndianess(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::getStateOfHealth(uint16_t &value) {
|
||||||
|
if (readRegister16(registers::CMD_STATE_OF_HEALTH, value)) {
|
||||||
|
swapEndianess(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::getChargeVoltageMax(uint16_t &value) {
|
||||||
|
if (readRegister16(registers::CMD_CHARGE_VOLTAGE, value)) {
|
||||||
|
swapEndianess(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::unsealDevice() {
|
||||||
|
uint8_t cmd1[] = {0x00, 0x14, 0x04};
|
||||||
|
if (!write(cmd1, ARRAYSIZE(cmd1))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||||
|
uint8_t cmd2[] = {0x00, 0x72, 0x36};
|
||||||
|
if (!write(cmd2, ARRAYSIZE(cmd2))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::unsealFullAccess()
|
||||||
|
{
|
||||||
|
uint8_t buffer[3];
|
||||||
|
buffer[0] = 0x00;
|
||||||
|
buffer[1] = lowByte((accessKey >> 24));
|
||||||
|
buffer[2] = lowByte((accessKey >> 16));
|
||||||
|
if (!write(buffer, ARRAYSIZE(buffer))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||||
|
buffer[1] = lowByte((accessKey >> 8));
|
||||||
|
buffer[2] = lowByte((accessKey));
|
||||||
|
if (!write(buffer, ARRAYSIZE(buffer))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::exitSealMode() {
|
||||||
|
return sendSubCommand(registers::SUBCMD_SEALED);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::sendSubCommand(uint16_t subCmd, bool waitConfirm)
|
||||||
|
{
|
||||||
|
uint8_t buffer[3];
|
||||||
|
buffer[0] = 0x00;
|
||||||
|
buffer[1] = lowByte(subCmd);
|
||||||
|
buffer[2] = highByte(subCmd);
|
||||||
|
if (!write(buffer, ARRAYSIZE(buffer))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!waitConfirm) {
|
||||||
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
constexpr uint8_t statusReg = 0x00;
|
||||||
|
int waitCount = 20;
|
||||||
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||||
|
while (waitCount--) {
|
||||||
|
writeRead(&statusReg, 1, buffer, 2);
|
||||||
|
uint16_t *value = reinterpret_cast<uint16_t *>(buffer);
|
||||||
|
if (*value == 0xFFA5) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
TT_LOG_E(TAG, "Subcommand x%X failed!", subCmd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::writeConfig16(uint16_t address, uint16_t value) {
|
||||||
|
constexpr uint8_t fixedDataLength = 0x06;
|
||||||
|
const uint8_t msbAccessValue = highByte(address);
|
||||||
|
const uint8_t lsbAccessValue = lowByte(address);
|
||||||
|
|
||||||
|
// Write to access the MSB of Capacity
|
||||||
|
writeRegister8(registers::ROM_START, msbAccessValue);
|
||||||
|
|
||||||
|
// Write to access the LSB of Capacity
|
||||||
|
writeRegister8(registers::ROM_START + 1, lsbAccessValue);
|
||||||
|
|
||||||
|
// Write two Capacity bytes starting from 0x40
|
||||||
|
uint8_t valueMsb = highByte(value);
|
||||||
|
uint8_t valueLsb = lowByte(value);
|
||||||
|
uint8_t configRaw[] = {valueMsb, valueLsb};
|
||||||
|
writeRegister(registers::MAC_BUFFER_START, configRaw, 2);
|
||||||
|
// Calculate new checksum
|
||||||
|
uint8_t checksum = 0xFF - ((msbAccessValue + lsbAccessValue + valueMsb + valueLsb) & 0xFF);
|
||||||
|
|
||||||
|
// Write new checksum (0x60)
|
||||||
|
writeRegister8(registers::MAC_DATA_SUM, checksum);
|
||||||
|
|
||||||
|
// Write the block length
|
||||||
|
writeRegister8(registers::MAC_DATA_LEN, fixedDataLength);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::configPreamble(bool &isSealed) {
|
||||||
|
int timeout = 0;
|
||||||
|
OperationStatus status;
|
||||||
|
|
||||||
|
// Check access settings
|
||||||
|
if(!getOperationStatus(status)) {
|
||||||
|
TT_LOG_E(TAG, "Cannot read initial operation status!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.reg.SEC == OperationStatusSecSealed) {
|
||||||
|
isSealed = true;
|
||||||
|
if (!unsealDevice()) {
|
||||||
|
TT_LOG_E(TAG, "Unsealing device failure!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.reg.SEC != OperationStatusSecFull) {
|
||||||
|
if (!unsealFullAccess()) {
|
||||||
|
TT_LOG_E(TAG, "Unsealing full access failure!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send ENTER_CFG_UPDATE command (0x0090)
|
||||||
|
if (!sendSubCommand(registers::SUBCMD_ENTER_CFG_UPDATE)) {
|
||||||
|
TT_LOG_E(TAG, "Config Update Subcommand failure!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm CFUPDATE mode by polling the OperationStatus() register until Bit 2 is set.
|
||||||
|
bool isConfigUpdate = false;
|
||||||
|
for (timeout = 30; timeout; --timeout) {
|
||||||
|
getOperationStatus(status);
|
||||||
|
if (status.reg.CFGUPDATE) {
|
||||||
|
isConfigUpdate = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
if (!isConfigUpdate) {
|
||||||
|
TT_LOG_E(TAG, "Update Mode timeout, maybe the access key for full permissions is invalid!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bq27220::configEpilouge(const bool isSealed) {
|
||||||
|
int timeout = 0;
|
||||||
|
OperationStatus status;
|
||||||
|
|
||||||
|
// Exit CFUPDATE mode by sending the EXIT_CFG_UPDATE_REINIT (0x0091) or EXIT_CFG_UPDATE (0x0092) command
|
||||||
|
sendSubCommand(registers::SUBCMD_EXIT_CFG_UPDATE_REINIT);
|
||||||
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||||
|
|
||||||
|
// Confirm that CFUPDATE mode has been exited by polling the OperationStatus() register until bit 2 is cleared
|
||||||
|
for (timeout = 60; timeout; --timeout) {
|
||||||
|
getOperationStatus(status);
|
||||||
|
if (!status.reg.CFGUPDATE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
if (timeout == 0) {
|
||||||
|
TT_LOG_E(TAG, "Timed out waiting to exit update mode.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the device was previously in SEALED state, return to SEALED mode by sending the Control(0x0030) subcommand
|
||||||
|
if (isSealed) {
|
||||||
|
TT_LOG_D(TAG, "Restore Safe Mode!");
|
||||||
|
exitSealMode();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
107
Drivers/BQ27220/Source/Bq27220.h
Normal file
107
Drivers/BQ27220/Source/Bq27220.h
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Tactility/hal/i2c/I2cDevice.h>
|
||||||
|
|
||||||
|
#define BQ27220_ADDRESS 0x55
|
||||||
|
|
||||||
|
class Bq27220 final : public tt::hal::i2c::I2cDevice {
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t accessKey;
|
||||||
|
|
||||||
|
bool unsealDevice();
|
||||||
|
bool unsealFullAccess();
|
||||||
|
bool exitSealMode();
|
||||||
|
bool sendSubCommand(uint16_t subCmd, bool waitConfirm = false);
|
||||||
|
bool writeConfig16(uint16_t address, uint16_t value);
|
||||||
|
bool configPreamble(bool &isSealed);
|
||||||
|
bool configEpilouge(const bool isSealed);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool performConfigUpdate(T configUpdateFunc)
|
||||||
|
{
|
||||||
|
bool isSealed = false;
|
||||||
|
|
||||||
|
if (!configPreamble(isSealed)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool result = configUpdateFunc();
|
||||||
|
configEpilouge(isSealed);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Register structures lifted from
|
||||||
|
// https://github.com/Xinyuan-LilyGO/T-Deck-Pro/blob/master/lib/BQ27220/bq27220.h
|
||||||
|
// Copyright (c) 2025 Liygo / Shenzhen Xinyuan Electronic Technology Co., Ltd
|
||||||
|
|
||||||
|
union BatteryStatus {
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
// Low byte, Low bit first
|
||||||
|
uint16_t DSG : 1; /**< The device is in DISCHARGE */
|
||||||
|
uint16_t SYSDWN : 1; /**< System down bit indicating the system should shut down */
|
||||||
|
uint16_t TDA : 1; /**< Terminate Discharge Alarm */
|
||||||
|
uint16_t BATTPRES : 1; /**< Battery Present detected */
|
||||||
|
uint16_t AUTH_GD : 1; /**< Detect inserted battery */
|
||||||
|
uint16_t OCVGD : 1; /**< Good OCV measurement taken */
|
||||||
|
uint16_t TCA : 1; /**< Terminate Charge Alarm */
|
||||||
|
uint16_t RSVD : 1; /**< Reserved */
|
||||||
|
// High byte, Low bit first
|
||||||
|
uint16_t CHGING : 1; /**< Charge inhibit */
|
||||||
|
uint16_t FC : 1; /**< Full-charged is detected */
|
||||||
|
uint16_t OTD : 1; /**< Overtemperature in discharge condition is detected */
|
||||||
|
uint16_t OTC : 1; /**< Overtemperature in charge condition is detected */
|
||||||
|
uint16_t SLEEP : 1; /**< Device is operating in SLEEP mode when set */
|
||||||
|
uint16_t OCVFALL : 1; /**< Status bit indicating that the OCV reading failed due to current */
|
||||||
|
uint16_t OCVCOMP : 1; /**< An OCV measurement update is complete */
|
||||||
|
uint16_t FD : 1; /**< Full-discharge is detected */
|
||||||
|
} reg;
|
||||||
|
uint16_t full;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum OperationStatusSec {
|
||||||
|
OperationStatusSecSealed = 0b11,
|
||||||
|
OperationStatusSecUnsealed = 0b10,
|
||||||
|
OperationStatusSecFull = 0b01,
|
||||||
|
};
|
||||||
|
|
||||||
|
union OperationStatus {
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
// Low byte, Low bit first
|
||||||
|
bool CALMD : 1; /**< Calibration mode enabled */
|
||||||
|
uint8_t SEC : 2; /**< Current security access */
|
||||||
|
bool EDV2 : 1; /**< EDV2 threshold exceeded */
|
||||||
|
bool VDQ : 1; /**< Indicates if Current discharge cycle is NOT qualified or qualified for an FCC updated */
|
||||||
|
bool INITCOMP : 1; /**< gauge initialization is complete */
|
||||||
|
bool SMTH : 1; /**< RemainingCapacity is scaled by smooth engine */
|
||||||
|
bool BTPINT : 1; /**< BTP threshold has been crossed */
|
||||||
|
// High byte, Low bit first
|
||||||
|
uint8_t RSVD1 : 2; /**< Reserved */
|
||||||
|
bool CFGUPDATE : 1; /**< Gauge is in CONFIG UPDATE mode */
|
||||||
|
uint8_t RSVD0 : 5; /**< Reserved */
|
||||||
|
} reg;
|
||||||
|
uint16_t full;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string getName() const final { return "BQ27220"; }
|
||||||
|
|
||||||
|
std::string getDescription() const final { return "I2C-controlled CEDV battery fuel gauge"; }
|
||||||
|
|
||||||
|
explicit Bq27220(i2c_port_t port) : I2cDevice(port, BQ27220_ADDRESS), accessKey(0xFFFFFFFF) {}
|
||||||
|
|
||||||
|
bool configureCapacity(uint16_t designCapacity, uint16_t fullChargeCapacity);
|
||||||
|
bool getVoltage(uint16_t &value);
|
||||||
|
bool getCurrent(int16_t &value);
|
||||||
|
bool getBatteryStatus(BatteryStatus &batt_sta);
|
||||||
|
bool getOperationStatus(OperationStatus &oper_sta);
|
||||||
|
bool getTemperature(uint16_t &value);
|
||||||
|
bool getFullChargeCapacity(uint16_t &value);
|
||||||
|
bool getDesignCapacity(uint16_t &value);
|
||||||
|
bool getRemainingCapacity(uint16_t &value);
|
||||||
|
bool getStateOfCharge(uint16_t &value);
|
||||||
|
bool getStateOfHealth(uint16_t &value);
|
||||||
|
bool getChargeVoltageMax(uint16_t &value);
|
||||||
|
};
|
||||||
5
Drivers/ST7796/CMakeLists.txt
Normal file
5
Drivers/ST7796/CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRC_DIRS "Source"
|
||||||
|
INCLUDE_DIRS "Source"
|
||||||
|
REQUIRES Tactility esp_lvgl_port esp_lcd_st7796 driver
|
||||||
|
)
|
||||||
3
Drivers/ST7796/README.md
Normal file
3
Drivers/ST7796/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# ST7796
|
||||||
|
|
||||||
|
A basic ESP32 LVGL driver for ST7796 displays.
|
||||||
210
Drivers/ST7796/Source/St7796Display.cpp
Normal file
210
Drivers/ST7796/Source/St7796Display.cpp
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
#include "St7796Display.h"
|
||||||
|
|
||||||
|
#include <Tactility/Log.h>
|
||||||
|
|
||||||
|
#include <esp_lcd_panel_commands.h>
|
||||||
|
#include <esp_lcd_panel_dev.h>
|
||||||
|
#include <esp_lcd_st7796.h>
|
||||||
|
#include <esp_lvgl_port.h>
|
||||||
|
|
||||||
|
#define TAG "st7796"
|
||||||
|
|
||||||
|
bool St7796Display::start() {
|
||||||
|
TT_LOG_I(TAG, "Starting");
|
||||||
|
|
||||||
|
const esp_lcd_panel_io_spi_config_t panel_io_config = {
|
||||||
|
.cs_gpio_num = configuration->csPin,
|
||||||
|
.dc_gpio_num = configuration->dcPin,
|
||||||
|
.spi_mode = 0,
|
||||||
|
.pclk_hz = configuration->pixelClockFrequency,
|
||||||
|
.trans_queue_depth = configuration->transactionQueueDepth,
|
||||||
|
.on_color_trans_done = nullptr,
|
||||||
|
.user_ctx = nullptr,
|
||||||
|
.lcd_cmd_bits = 8,
|
||||||
|
.lcd_param_bits = 8,
|
||||||
|
.cs_ena_pretrans = 0,
|
||||||
|
.cs_ena_posttrans = 0,
|
||||||
|
.flags = {
|
||||||
|
.dc_high_on_cmd = 0,
|
||||||
|
.dc_low_on_data = 0,
|
||||||
|
.dc_low_on_param = 0,
|
||||||
|
.octal_mode = 0,
|
||||||
|
.quad_mode = 0,
|
||||||
|
.sio_mode = 0,
|
||||||
|
.lsb_first = 0,
|
||||||
|
.cs_high_active = 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (esp_lcd_new_panel_io_spi(configuration->spiBusHandle, &panel_io_config, &ioHandle) != ESP_OK) {
|
||||||
|
TT_LOG_E(TAG, "Failed to create panel");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const st7796_lcd_init_cmd_t lcd_init_cmds[] = {
|
||||||
|
{0x01, (uint8_t[]) {0x00}, 0, 120},
|
||||||
|
{0x11, (uint8_t[]) {0x00}, 0, 120},
|
||||||
|
{0xF0, (uint8_t[]) {0xC3}, 1, 0},
|
||||||
|
{0xF0, (uint8_t[]) {0xC3}, 1, 0},
|
||||||
|
{0xF0, (uint8_t[]) {0x96}, 1, 0},
|
||||||
|
{0x36, (uint8_t[]) {0x58}, 1, 0},
|
||||||
|
{0x3A, (uint8_t[]) {0x55}, 1, 0},
|
||||||
|
{0xB4, (uint8_t[]) {0x01}, 1, 0},
|
||||||
|
{0xB6, (uint8_t[]) {0x80, 0x02, 0x3B}, 3, 0},
|
||||||
|
{0xE8, (uint8_t[]) {0x40, 0x8A, 0x00, 0x00, 0x29, 0x19, 0xA5, 0x33}, 8, 0},
|
||||||
|
{0xC1, (uint8_t[]) {0x06}, 1, 0},
|
||||||
|
{0xC2, (uint8_t[]) {0xA7}, 1, 0},
|
||||||
|
{0xC5, (uint8_t[]) {0x18}, 1, 0},
|
||||||
|
{0xE0, (uint8_t[]) {0xF0, 0x09, 0x0b, 0x06, 0x04, 0x15, 0x2F, 0x54, 0x42, 0x3C, 0x17, 0x14, 0x18, 0x1B}, 15, 0},
|
||||||
|
{0xE1, (uint8_t[]) {0xE0, 0x09, 0x0b, 0x06, 0x04, 0x03, 0x2B, 0x43, 0x42, 0x3B, 0x16, 0x14, 0x17, 0x1B}, 15, 120},
|
||||||
|
{0xF0, (uint8_t[]) {0x3C}, 1, 0},
|
||||||
|
{0xF0, (uint8_t[]) {0x69}, 1, 0},
|
||||||
|
{0x21, (uint8_t[]) {0x00}, 1, 0},
|
||||||
|
{0x29, (uint8_t[]) {0x00}, 1, 0},
|
||||||
|
};
|
||||||
|
|
||||||
|
st7796_vendor_config_t vendor_config = {
|
||||||
|
// Uncomment these lines if use custom initialization commands
|
||||||
|
.init_cmds = lcd_init_cmds,
|
||||||
|
.init_cmds_size = sizeof(lcd_init_cmds) / sizeof(st7796_lcd_init_cmd_t),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const esp_lcd_panel_dev_config_t panel_config = {
|
||||||
|
.reset_gpio_num = configuration->resetPin, // Set to -1 if not use
|
||||||
|
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||||
|
.color_space = ESP_LCD_COLOR_SPACE_RGB,
|
||||||
|
#else
|
||||||
|
.color_space = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||||
|
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
|
||||||
|
#endif
|
||||||
|
.bits_per_pixel = 16,
|
||||||
|
.vendor_config = &vendor_config
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
const esp_lcd_panel_dev_config_t panel_config = {
|
||||||
|
.reset_gpio_num = configuration->resetPin,
|
||||||
|
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||||
|
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
|
||||||
|
.bits_per_pixel = 16,
|
||||||
|
.flags = {
|
||||||
|
.reset_active_high = false
|
||||||
|
},
|
||||||
|
.vendor_config = nullptr
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
if (esp_lcd_new_panel_st7796(ioHandle, &panel_config, &panelHandle) != ESP_OK) {
|
||||||
|
TT_LOG_E(TAG, "Failed to create panel");
|
||||||
|
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_invert_color(panelHandle, configuration->invertColor) != ESP_OK) {
|
||||||
|
TT_LOG_E(TAG, "Failed to set panel to invert");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esp_lcd_panel_swap_xy(panelHandle, configuration->swapXY) != ESP_OK) {
|
||||||
|
TT_LOG_E(TAG, "Failed to swap XY ");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esp_lcd_panel_mirror(panelHandle, configuration->mirrorX, configuration->mirrorY) != ESP_OK) {
|
||||||
|
TT_LOG_E(TAG, "Failed to set panel to mirror");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esp_lcd_panel_set_gap(panelHandle, configuration->gapX, configuration->gapY) != ESP_OK) {
|
||||||
|
TT_LOG_E(TAG, "Failed to set panel gap");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esp_lcd_panel_disp_on_off(panelHandle, true) != ESP_OK) {
|
||||||
|
TT_LOG_E(TAG, "Failed to turn display on");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t buffer_size;
|
||||||
|
if (configuration->bufferSize == 0) {
|
||||||
|
buffer_size = configuration->horizontalResolution * configuration->verticalResolution / 10;
|
||||||
|
} else {
|
||||||
|
buffer_size = configuration->bufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lvgl_port_display_cfg_t disp_cfg = {
|
||||||
|
.io_handle = ioHandle,
|
||||||
|
.panel_handle = panelHandle,
|
||||||
|
.control_handle = nullptr,
|
||||||
|
.buffer_size = buffer_size,
|
||||||
|
.double_buffer = false,
|
||||||
|
.trans_size = 0,
|
||||||
|
.hres = configuration->horizontalResolution,
|
||||||
|
.vres = configuration->verticalResolution,
|
||||||
|
.monochrome = false,
|
||||||
|
.rotation = {
|
||||||
|
.swap_xy = configuration->swapXY,
|
||||||
|
.mirror_x = configuration->mirrorX,
|
||||||
|
.mirror_y = configuration->mirrorY,
|
||||||
|
},
|
||||||
|
.color_format = LV_COLOR_FORMAT_NATIVE,
|
||||||
|
.flags = {.buff_dma = true, .buff_spiram = false, .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 St7796Display::stop() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void St7796Display::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");
|
||||||
|
}*/
|
||||||
|
}
|
||||||
103
Drivers/ST7796/Source/St7796Display.h
Normal file
103
Drivers/ST7796/Source/St7796Display.h
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Tactility/hal/display/DisplayDevice.h"
|
||||||
|
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <driver/spi_common.h>
|
||||||
|
#include <esp_lcd_panel_io.h>
|
||||||
|
#include <esp_lcd_types.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
class St7796Display final : public tt::hal::display::DisplayDevice {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
class Configuration {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Configuration(
|
||||||
|
esp_lcd_spi_bus_handle_t spi_bus_handle,
|
||||||
|
gpio_num_t csPin,
|
||||||
|
gpio_num_t dcPin,
|
||||||
|
unsigned int horizontalResolution,
|
||||||
|
unsigned int verticalResolution,
|
||||||
|
std::shared_ptr<tt::hal::touch::TouchDevice> touch,
|
||||||
|
bool swapXY = false,
|
||||||
|
bool mirrorX = false,
|
||||||
|
bool mirrorY = false,
|
||||||
|
bool invertColor = false,
|
||||||
|
unsigned int gapX = 0,
|
||||||
|
unsigned int gapY = 0,
|
||||||
|
uint32_t bufferSize = 0 // Size in pixel count. 0 means default, which is 1/10 of the screen size
|
||||||
|
) : spiBusHandle(spi_bus_handle),
|
||||||
|
csPin(csPin),
|
||||||
|
dcPin(dcPin),
|
||||||
|
horizontalResolution(horizontalResolution),
|
||||||
|
verticalResolution(verticalResolution),
|
||||||
|
swapXY(swapXY),
|
||||||
|
mirrorX(mirrorX),
|
||||||
|
mirrorY(mirrorY),
|
||||||
|
invertColor(invertColor),
|
||||||
|
gapX(gapX),
|
||||||
|
gapY(gapY),
|
||||||
|
bufferSize(bufferSize),
|
||||||
|
touch(std::move(touch)) {}
|
||||||
|
|
||||||
|
esp_lcd_spi_bus_handle_t spiBusHandle;
|
||||||
|
gpio_num_t csPin;
|
||||||
|
gpio_num_t dcPin;
|
||||||
|
gpio_num_t resetPin = GPIO_NUM_NC;
|
||||||
|
unsigned int pixelClockFrequency = 80'000'000; // Hertz
|
||||||
|
size_t transactionQueueDepth = 2;
|
||||||
|
unsigned int horizontalResolution;
|
||||||
|
unsigned int verticalResolution;
|
||||||
|
bool swapXY = false;
|
||||||
|
bool mirrorX = false;
|
||||||
|
bool mirrorY = false;
|
||||||
|
bool invertColor = false;
|
||||||
|
unsigned int gapX = 0;
|
||||||
|
unsigned int gapY = 0;
|
||||||
|
uint32_t bufferSize = 0; // Size in pixel count. 0 means default, which is 1/10 of the screen size
|
||||||
|
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
|
||||||
|
std::function<void(uint8_t)> _Nullable backlightDutyFunction = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::unique_ptr<Configuration> configuration;
|
||||||
|
esp_lcd_panel_io_handle_t ioHandle = nullptr;
|
||||||
|
esp_lcd_panel_handle_t panelHandle = nullptr;
|
||||||
|
lv_display_t* displayHandle = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit St7796Display(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
|
||||||
|
assert(configuration != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getName() const final { return "ST7796"; }
|
||||||
|
std::string getDescription() const final { return "ST7796 display"; }
|
||||||
|
|
||||||
|
bool start() final;
|
||||||
|
|
||||||
|
bool stop() final;
|
||||||
|
|
||||||
|
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable createTouch() final { return configuration->touch; }
|
||||||
|
|
||||||
|
void setBacklightDuty(uint8_t backlightDuty) final {
|
||||||
|
if (configuration->backlightDutyFunction != nullptr) {
|
||||||
|
configuration->backlightDutyFunction(backlightDuty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGammaCurve(uint8_t index) final;
|
||||||
|
uint8_t getGammaCurveCount() const final { return 4; };
|
||||||
|
|
||||||
|
bool supportsBacklightDuty() const final { return configuration->backlightDutyFunction != nullptr; }
|
||||||
|
|
||||||
|
lv_display_t* _Nullable getLvglDisplay() const final { return displayHandle; }
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
||||||
5
Drivers/TCA8418/CMakeLists.txt
Normal file
5
Drivers/TCA8418/CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRC_DIRS "Source"
|
||||||
|
INCLUDE_DIRS "Source"
|
||||||
|
REQUIRES Tactility
|
||||||
|
)
|
||||||
18
Drivers/TCA8418/COPYRIGHT.md
Normal file
18
Drivers/TCA8418/COPYRIGHT.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
Copyright 2023 Anthony DiGirolamo
|
||||||
|
|
||||||
|
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.
|
||||||
4
Drivers/TCA8418/README.md
Normal file
4
Drivers/TCA8418/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# TCA8418 I2C Controlled Keypad Scan IC With Integrated ESD Protection
|
||||||
|
|
||||||
|
[Datasheet](https://www.ti.com/lit/ds/symlink/tca8418.pdf?ts=1751500237439)
|
||||||
|
[Original implementation](https://github.com/AnthonyDiGirolamo/i2c-thumb-keyboard/tree/master) by Anthony DiGirolamo
|
||||||
202
Drivers/TCA8418/Source/Tca8418.cpp
Normal file
202
Drivers/TCA8418/Source/Tca8418.cpp
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
#include "Tca8418.h"
|
||||||
|
#include <Tactility/Log.h>
|
||||||
|
|
||||||
|
#define TAG "tca8418"
|
||||||
|
|
||||||
|
namespace registers {
|
||||||
|
static const uint8_t CFG = 0x01U;
|
||||||
|
static const uint8_t KP_GPIO1 = 0x1DU;
|
||||||
|
static const uint8_t KP_GPIO2 = 0x1EU;
|
||||||
|
static const uint8_t KP_GPIO3 = 0x1FU;
|
||||||
|
|
||||||
|
static const uint8_t KEY_EVENT_A = 0x04U;
|
||||||
|
static const uint8_t KEY_EVENT_B = 0x05U;
|
||||||
|
static const uint8_t KEY_EVENT_C = 0x06U;
|
||||||
|
static const uint8_t KEY_EVENT_D = 0x07U;
|
||||||
|
static const uint8_t KEY_EVENT_E = 0x08U;
|
||||||
|
static const uint8_t KEY_EVENT_F = 0x09U;
|
||||||
|
static const uint8_t KEY_EVENT_G = 0x0AU;
|
||||||
|
static const uint8_t KEY_EVENT_H = 0x0BU;
|
||||||
|
static const uint8_t KEY_EVENT_I = 0x0CU;
|
||||||
|
static const uint8_t KEY_EVENT_J = 0x0DU;
|
||||||
|
} // namespace registers
|
||||||
|
|
||||||
|
|
||||||
|
void Tca8418::init(uint8_t numrows, uint8_t numcols) {
|
||||||
|
/*
|
||||||
|
* | ADDRESS | REGISTER NAME | REGISTER DESCRIPTION | BIT7 | BIT6 | BIT5 | BIT4 | BIT3 | BIT2 | BIT1 | BIT0 |
|
||||||
|
* |---------+---------------+----------------------+------+------+------+------+------+------+------+------|
|
||||||
|
* | 0x1D | KP_GPIO1 | Keypad/GPIO Select 1 | ROW7 | ROW6 | ROW5 | ROW4 | ROW3 | ROW2 | ROW1 | ROW0 |
|
||||||
|
* | 0x1E | KP_GPIO2 | Keypad/GPIO Select 2 | COL7 | COL6 | COL5 | COL4 | COL3 | COL2 | COL1 | COL0 |
|
||||||
|
* | 0x1F | KP_GPIO3 | Keypad/GPIO Select 3 | N/A | N/A | N/A | N/A | N/A | N/A | COL9 | COL8 |
|
||||||
|
*/
|
||||||
|
|
||||||
|
num_rows = numrows;
|
||||||
|
num_cols = numcols;
|
||||||
|
|
||||||
|
// everything enabled in key scan mode
|
||||||
|
uint8_t enabled_rows = 0x3F;
|
||||||
|
uint16_t enabled_cols = 0x3FF;
|
||||||
|
|
||||||
|
writeRegister8(registers::KP_GPIO1, enabled_rows);
|
||||||
|
writeRegister8(registers::KP_GPIO2, (uint8_t)(0xFF & enabled_cols));
|
||||||
|
writeRegister8(registers::KP_GPIO3, (uint8_t)(0x03 & (enabled_cols >> 8)));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BIT: NAME
|
||||||
|
*
|
||||||
|
* 7: AI
|
||||||
|
* Auto-increment for read and write operations; See below table for more information
|
||||||
|
* 0 = disabled
|
||||||
|
* 1 = enabled
|
||||||
|
*
|
||||||
|
* 6: GPI_E_CFG
|
||||||
|
* GPI event mode configuration
|
||||||
|
* 0 = GPI events are tracked when keypad is locked
|
||||||
|
* 1 = GPI events are not tracked when keypad is locked
|
||||||
|
*
|
||||||
|
* 5: OVR_FLOW_M
|
||||||
|
* Overflow mode
|
||||||
|
* 0 = disabled; Overflow data is lost
|
||||||
|
* 1 = enabled; Overflow data shifts with last event pushing first event out
|
||||||
|
*
|
||||||
|
* 4: INT_CFG
|
||||||
|
* Interrupt configuration
|
||||||
|
* 0 = processor interrupt remains asserted (or low) if host tries to clear interrupt while there is
|
||||||
|
* still a pending key press, key release or GPI interrupt
|
||||||
|
* 1 = processor interrupt is deasserted for 50 μs and reassert with pending interrupts
|
||||||
|
*
|
||||||
|
* 3: OVR_FLOW_IEN
|
||||||
|
* Overflow interrupt enable
|
||||||
|
* 0 = disabled; INT is not asserted if the FIFO overflows
|
||||||
|
* 1 = enabled; INT becomes asserted if the FIFO overflows
|
||||||
|
*
|
||||||
|
* 2: K_LCK_IEN
|
||||||
|
* Keypad lock interrupt enable
|
||||||
|
* 0 = disabled; INT is not asserted after a correct unlock key sequence
|
||||||
|
* 1 = enabled; INT becomes asserted after a correct unlock key sequence
|
||||||
|
*
|
||||||
|
* 1: GPI_IEN
|
||||||
|
* GPI interrupt enable to host processor
|
||||||
|
* 0 = disabled; INT is not asserted for a change on a GPI
|
||||||
|
* 1 = enabled; INT becomes asserted for a change on a GPI
|
||||||
|
*
|
||||||
|
* 0: KE_IEN
|
||||||
|
* Key events interrupt enable to host processor
|
||||||
|
* 0 = disabled; INT is not asserted when a key event occurs
|
||||||
|
* 1 = enabled; INT becomes asserted when a key event occurs
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 10111001 xB9 -- fifo overflow enabled
|
||||||
|
// 10011001 x99 -- fifo overflow disabled
|
||||||
|
writeRegister8(registers::CFG, 0x99);
|
||||||
|
|
||||||
|
clear_released_list();
|
||||||
|
clear_pressed_list();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Tca8418::update() {
|
||||||
|
last_update_micros = this_update_micros;
|
||||||
|
uint8_t key_code, key_down, key_event, key_row, key_col;
|
||||||
|
|
||||||
|
key_event = get_key_event();
|
||||||
|
// TODO: read gpio R7/R6 status? 0x14 bits 7&6
|
||||||
|
// read(0x14, &new_keycode)
|
||||||
|
|
||||||
|
// TODO: use tick function to get an update delta time
|
||||||
|
this_update_micros = 0;
|
||||||
|
delta_micros = this_update_micros - last_update_micros;
|
||||||
|
|
||||||
|
if (key_event > 0) {
|
||||||
|
key_code = key_event & 0x7F;
|
||||||
|
key_down = (key_event & 0x80) >> 7;
|
||||||
|
key_row = key_code / num_cols;
|
||||||
|
key_col = key_code % num_cols;
|
||||||
|
|
||||||
|
// always clear the released list
|
||||||
|
clear_released_list();
|
||||||
|
|
||||||
|
if (key_down) {
|
||||||
|
add_pressed_key(key_row, key_col);
|
||||||
|
// TODO reject ghosts (assume multiple key presses with the same hold time are ghosts.)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
add_released_key(key_row, key_col);
|
||||||
|
remove_pressed_key(key_row, key_col);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment hold times for pressed keys
|
||||||
|
for (int i = 0; i < pressed_key_count; i++) {
|
||||||
|
pressed_list[i].hold_time += delta_micros;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Tca8418::add_pressed_key(uint8_t row, uint8_t col) {
|
||||||
|
if (pressed_key_count >= KEY_EVENT_LIST_SIZE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
pressed_list[pressed_key_count].row = row;
|
||||||
|
pressed_list[pressed_key_count].col = col;
|
||||||
|
pressed_list[pressed_key_count].hold_time = 0;
|
||||||
|
pressed_key_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tca8418::add_released_key(uint8_t row, uint8_t col) {
|
||||||
|
if (released_key_count >= KEY_EVENT_LIST_SIZE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
released_key_count++;
|
||||||
|
released_list[0].row = row;
|
||||||
|
released_list[0].col = col;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tca8418::remove_pressed_key(uint8_t row, uint8_t col) {
|
||||||
|
if (pressed_key_count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// delete the pressed key
|
||||||
|
for (int i = 0; i < pressed_key_count; i++) {
|
||||||
|
if (pressed_list[i].row == row &&
|
||||||
|
pressed_list[i].col == col) {
|
||||||
|
// shift remaining keys left one index
|
||||||
|
for (int j = i; i < pressed_key_count; j++) {
|
||||||
|
if (j == KEY_EVENT_LIST_SIZE - 1)
|
||||||
|
break;
|
||||||
|
pressed_list[j].row = pressed_list[j + 1].row;
|
||||||
|
pressed_list[j].col = pressed_list[j + 1].col;
|
||||||
|
pressed_list[j].hold_time = pressed_list[j + 1].hold_time;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pressed_key_count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tca8418::clear_pressed_list() {
|
||||||
|
for (int i = 0; i < KEY_EVENT_LIST_SIZE; i++) {
|
||||||
|
pressed_list[i].row = 255;
|
||||||
|
pressed_list[i].col = 255;
|
||||||
|
}
|
||||||
|
pressed_key_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tca8418::clear_released_list() {
|
||||||
|
for (int i = 0; i < KEY_EVENT_LIST_SIZE; i++) {
|
||||||
|
released_list[i].row = 255;
|
||||||
|
released_list[i].col = 255;
|
||||||
|
}
|
||||||
|
released_key_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Tca8418::get_key_event() {
|
||||||
|
uint8_t new_keycode = 0;
|
||||||
|
|
||||||
|
readRegister8(registers::KEY_EVENT_A, new_keycode);
|
||||||
|
return new_keycode;
|
||||||
|
}
|
||||||
69
Drivers/TCA8418/Source/Tca8418.h
Normal file
69
Drivers/TCA8418/Source/Tca8418.h
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include <Tactility/hal/i2c/I2cDevice.h>
|
||||||
|
|
||||||
|
#define TCA8418_ADDRESS 0x34U
|
||||||
|
#define KEY_EVENT_LIST_SIZE 10
|
||||||
|
|
||||||
|
class Tca8418 final : public tt::hal::i2c::I2cDevice {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
uint8_t tca8418_address;
|
||||||
|
uint32_t last_update_micros;
|
||||||
|
uint32_t this_update_micros;
|
||||||
|
|
||||||
|
uint8_t new_pressed_keys_count;
|
||||||
|
|
||||||
|
void clear_released_list();
|
||||||
|
void clear_pressed_list();
|
||||||
|
void add_pressed_key(uint8_t row, uint8_t col);
|
||||||
|
void add_released_key(uint8_t row, uint8_t col);
|
||||||
|
void remove_pressed_key(uint8_t row, uint8_t col);
|
||||||
|
void write(uint8_t register_address, uint8_t data);
|
||||||
|
bool read(uint8_t register_address, uint8_t* data);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
struct PressedKey {
|
||||||
|
uint8_t row;
|
||||||
|
uint8_t col;
|
||||||
|
uint32_t hold_time;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ReleasedKey {
|
||||||
|
uint8_t row;
|
||||||
|
uint8_t col;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string getName() const final { return "TCA8418"; }
|
||||||
|
|
||||||
|
std::string getDescription() const final { return "I2C-controlled keyboard scan IC"; }
|
||||||
|
|
||||||
|
explicit Tca8418(i2c_port_t port) : I2cDevice(port, TCA8418_ADDRESS) {
|
||||||
|
delta_micros = 0;
|
||||||
|
last_update_micros = 0;
|
||||||
|
this_update_micros = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
~Tca8418() {}
|
||||||
|
|
||||||
|
uint8_t num_rows;
|
||||||
|
uint8_t num_cols;
|
||||||
|
|
||||||
|
uint32_t delta_micros;
|
||||||
|
|
||||||
|
std::array<PressedKey, KEY_EVENT_LIST_SIZE> pressed_list;
|
||||||
|
std::array<ReleasedKey, KEY_EVENT_LIST_SIZE> released_list;
|
||||||
|
uint8_t pressed_key_count;
|
||||||
|
uint8_t released_key_count;
|
||||||
|
|
||||||
|
void init(uint8_t numrows, uint8_t numcols);
|
||||||
|
bool update();
|
||||||
|
uint8_t get_key_event();
|
||||||
|
bool button_pressed(uint8_t row, uint8_t button_bit_position);
|
||||||
|
bool button_released(uint8_t row, uint8_t button_bit_position);
|
||||||
|
bool button_held(uint8_t row, uint8_t button_bit_position);
|
||||||
|
};
|
||||||
@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "I2c.h"
|
|
||||||
#include "../Device.h"
|
#include "../Device.h"
|
||||||
|
#include "I2c.h"
|
||||||
|
|
||||||
namespace tt::hal::i2c {
|
namespace tt::hal::i2c {
|
||||||
|
|
||||||
@ -20,7 +20,11 @@ protected:
|
|||||||
|
|
||||||
static constexpr TickType_t DEFAULT_TIMEOUT = 1000 / portTICK_PERIOD_MS;
|
static constexpr TickType_t DEFAULT_TIMEOUT = 1000 / portTICK_PERIOD_MS;
|
||||||
|
|
||||||
|
bool read(uint8_t* data, size_t dataSize, TickType_t timeout = DEFAULT_TIMEOUT);
|
||||||
|
bool write(const uint8_t* data, uint16_t dataSize, TickType_t timeout = DEFAULT_TIMEOUT);
|
||||||
|
bool writeRead(const uint8_t* writeData, size_t writeDataSize, uint8_t* readData, size_t readDataSize, TickType_t timeout = DEFAULT_TIMEOUT);
|
||||||
bool readRegister8(uint8_t reg, uint8_t& result) const;
|
bool readRegister8(uint8_t reg, uint8_t& result) const;
|
||||||
|
bool writeRegister(uint8_t reg, const uint8_t* data, uint16_t dataSize, TickType_t timeout = DEFAULT_TIMEOUT);
|
||||||
bool writeRegister8(uint8_t reg, uint8_t value) const;
|
bool writeRegister8(uint8_t reg, uint8_t value) const;
|
||||||
bool readRegister12(uint8_t reg, float& out) const;
|
bool readRegister12(uint8_t reg, float& out) const;
|
||||||
bool readRegister14(uint8_t reg, float& out) const;
|
bool readRegister14(uint8_t reg, float& out) const;
|
||||||
@ -41,4 +45,4 @@ public:
|
|||||||
uint8_t getAddress() const { return address; }
|
uint8_t getAddress() const { return address; }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
} // namespace tt::hal::i2c
|
||||||
|
|||||||
@ -4,6 +4,22 @@
|
|||||||
|
|
||||||
namespace tt::hal::i2c {
|
namespace tt::hal::i2c {
|
||||||
|
|
||||||
|
bool I2cDevice::read(uint8_t* data, size_t dataSize, TickType_t timeout) {
|
||||||
|
return tt::hal::i2c::masterRead(port, address, data, dataSize, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool I2cDevice::write(const uint8_t* data, uint16_t dataSize, TickType_t timeout) {
|
||||||
|
return tt::hal::i2c::masterWrite(port, address, data, dataSize, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool I2cDevice::writeRead(const uint8_t* writeData, size_t writeDataSize, uint8_t* readData, size_t readDataSize, TickType_t timeout) {
|
||||||
|
return masterWriteRead(port, address, writeData, writeDataSize, readData, readDataSize, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool I2cDevice::writeRegister(uint8_t reg, const uint8_t* data, uint16_t dataSize, TickType_t timeout) {
|
||||||
|
return masterWriteRegister(port, address, reg, data, dataSize, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
bool I2cDevice::readRegister12(uint8_t reg, float& out) const {
|
bool I2cDevice::readRegister12(uint8_t reg, float& out) const {
|
||||||
std::uint8_t data[2] = {0};
|
std::uint8_t data[2] = {0};
|
||||||
if (tt::hal::i2c::masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) {
|
if (tt::hal::i2c::masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) {
|
||||||
@ -62,4 +78,4 @@ bool I2cDevice::bitOff(uint8_t reg, uint8_t bitmask) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace tt::hal::i2c
|
||||||
|
|||||||
@ -39,6 +39,17 @@ void redraw(Gui* gui) {
|
|||||||
|
|
||||||
if (gui->appToRender != nullptr) {
|
if (gui->appToRender != nullptr) {
|
||||||
|
|
||||||
|
// Create a default group which adds all objects automatically,
|
||||||
|
// and assign all indevs to it.
|
||||||
|
// This enables navigation with limited input, such as encoder wheels.
|
||||||
|
lv_group_t* group = lv_group_create();
|
||||||
|
auto* indev = lv_indev_get_next(nullptr);
|
||||||
|
while (indev) {
|
||||||
|
lv_indev_set_group(indev, group);
|
||||||
|
indev = lv_indev_get_next(indev);
|
||||||
|
}
|
||||||
|
lv_group_set_default(group);
|
||||||
|
|
||||||
app::Flags flags = std::static_pointer_cast<app::AppInstance>(gui->appToRender)->getFlags();
|
app::Flags flags = std::static_pointer_cast<app::AppInstance>(gui->appToRender)->getFlags();
|
||||||
if (flags.showStatusbar) {
|
if (flags.showStatusbar) {
|
||||||
lv_obj_remove_flag(gui->statusbarWidget, LV_OBJ_FLAG_HIDDEN);
|
lv_obj_remove_flag(gui->statusbarWidget, LV_OBJ_FLAG_HIDDEN);
|
||||||
@ -61,4 +72,4 @@ void redraw(Gui* gui) {
|
|||||||
unlock();
|
unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace tt::service::gui
|
||||||
|
|||||||
56
sdkconfig.board.lilygo-tlora-pager
Normal file
56
sdkconfig.board.lilygo-tlora-pager
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Software defaults
|
||||||
|
# Increase stack size for WiFi (fixes crash after scan)
|
||||||
|
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072
|
||||||
|
CONFIG_LV_FONT_MONTSERRAT_14=y
|
||||||
|
CONFIG_LV_FONT_MONTSERRAT_18=y
|
||||||
|
CONFIG_LV_USE_USER_DATA=y
|
||||||
|
CONFIG_LV_USE_FS_STDIO=y
|
||||||
|
CONFIG_LV_FS_STDIO_LETTER=65
|
||||||
|
CONFIG_LV_FS_STDIO_PATH=""
|
||||||
|
CONFIG_LV_FS_STDIO_CACHE_SIZE=4096
|
||||||
|
CONFIG_LV_USE_LODEPNG=y
|
||||||
|
CONFIG_LV_USE_BUILTIN_MALLOC=n
|
||||||
|
CONFIG_LV_USE_CLIB_MALLOC=y
|
||||||
|
CONFIG_LV_USE_MSGBOX=n
|
||||||
|
CONFIG_LV_USE_SPINNER=n
|
||||||
|
CONFIG_LV_USE_WIN=n
|
||||||
|
CONFIG_LV_USE_SNAPSHOT=y
|
||||||
|
CONFIG_FREERTOS_HZ=1000
|
||||||
|
CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2
|
||||||
|
CONFIG_FREERTOS_SMP=n
|
||||||
|
CONFIG_FREERTOS_UNICORE=n
|
||||||
|
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096
|
||||||
|
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
|
||||||
|
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||||
|
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||||
|
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
|
||||||
|
CONFIG_FATFS_LFN_HEAP=y
|
||||||
|
CONFIG_FATFS_VOLUME_COUNT=3
|
||||||
|
|
||||||
|
# Hardware: Main
|
||||||
|
CONFIG_TT_BOARD_LILYGO_TLORA_PAGER=y
|
||||||
|
CONFIG_TT_BOARD_NAME="LilyGo T-Lora Pager"
|
||||||
|
CONFIG_TT_BOARD_ID="lilygo-tlora-pager"
|
||||||
|
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
||||||
|
CONFIG_IDF_TARGET="esp32s3"
|
||||||
|
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
|
||||||
|
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
|
||||||
|
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
|
||||||
|
CONFIG_FLASHMODE_DIO=y
|
||||||
|
# Hardware: SPI RAM
|
||||||
|
CONFIG_ESP32S3_SPIRAM_SUPPORT=y
|
||||||
|
#CONFIG_SPIRAM_MODE_OCT=y
|
||||||
|
CONFIG_SPIRAM_TYPE_AUTO=y
|
||||||
|
CONFIG_SPIRAM_SPEED_120M=y
|
||||||
|
#CONFIG_SPIRAM_BOOT_INIT=y
|
||||||
|
CONFIG_SPIRAM_USE_MALLOC=y
|
||||||
|
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
|
||||||
|
# SPI Flash (can set back to 80MHz after ESP-IDF bug is resolved)
|
||||||
|
CONFIG_ESPTOOLPY_FLASHFREQ_40M=y
|
||||||
|
# LVGL
|
||||||
|
CONFIG_LV_DPI_DEF=90
|
||||||
|
CONFIG_LV_DISP_DEF_REFR_PERIOD=10
|
||||||
|
CONFIG_LV_THEME_DEFAULT_DARK=y
|
||||||
|
# USB
|
||||||
|
CONFIG_TINYUSB_MSC_ENABLED=y
|
||||||
|
CONFIG_TINYUSB_MSC_MOUNT_PATH="/sdcard"
|
||||||
Loading…
x
Reference in New Issue
Block a user