Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f775a59409 | |||
| dc5bae5448 | |||
| b9d6ef5884 |
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
|
||||
)
|
||||
80
Boards/LilygoTLoraPager/Source/Init.cpp
Normal file
80
Boards/LilygoTLoraPager/Source/Init.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
#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;
|
||||
|
||||
static bool powerOn() {
|
||||
/*
|
||||
gpio_config_t device_power_signal_config = {
|
||||
.pin_bit_mask = BIT64(TDECK_POWERON_GPIO),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
|
||||
if (gpio_config(&device_power_signal_config) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gpio_set_level(TDECK_POWERON_GPIO, 1) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tdeckInit() {
|
||||
ESP_LOGI(TAG, LOG_MESSAGE_POWER_ON_START);
|
||||
if (!powerOn()) {
|
||||
TT_LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
91
Boards/LilygoTLoraPager/Source/LilygoTloraPager.cpp
Normal file
91
Boards/LilygoTLoraPager/Source/LilygoTloraPager.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
#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 TDECK_SPI_TRANSFER_SIZE_LIMIT (TDECK_LCD_HORIZONTAL_RESOLUTION * TDECK_LCD_SPI_TRANSFER_HEIGHT * (LV_COLOR_DEPTH / 8))
|
||||
|
||||
bool tdeckInit();
|
||||
|
||||
using namespace tt::hal;
|
||||
|
||||
extern const Configuration lilygo_tlora_pager = {
|
||||
.initBoot = tdeckInit,
|
||||
.createDisplay = createDisplay,
|
||||
.createKeyboard = createKeyboard,
|
||||
.sdcard = createTpagerSdCard(),
|
||||
.power = tpager_get_power,
|
||||
.i2c = {
|
||||
i2c::Configuration {
|
||||
.name = "Internal",
|
||||
.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 = TDECK_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;
|
||||
41
Boards/LilygoTLoraPager/Source/hal/TpagerDisplay.cpp
Normal file
41
Boards/LilygoTLoraPager/Source/hal/TpagerDisplay.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
#include "TpagerDisplay.h"
|
||||
#include "TpagerDisplayConstants.h"
|
||||
|
||||
#include <St7796Display.h>
|
||||
#include <PwmBacklight.h>
|
||||
|
||||
#include <driver/spi_master.h>
|
||||
|
||||
#define TAG "tdeck_display"
|
||||
|
||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
||||
/*auto configuration = std::make_unique<St7796Display::Configuration>(
|
||||
TDECK_LCD_SPI_HOST,
|
||||
TDECK_LCD_PIN_CS,
|
||||
TDECK_LCD_PIN_DC,
|
||||
480,
|
||||
222,
|
||||
nullptr,
|
||||
true, //swapXY
|
||||
true, //mirrorX
|
||||
true, //mirrorY
|
||||
true //invertColor
|
||||
);*/
|
||||
|
||||
auto configuration = std::make_unique<St7796Display::Configuration>(
|
||||
TDECK_LCD_SPI_HOST,
|
||||
TDECK_LCD_PIN_CS,
|
||||
TDECK_LCD_PIN_DC,
|
||||
480, // w
|
||||
222, // h
|
||||
nullptr,
|
||||
true, //swapXY
|
||||
true, //mirrorX
|
||||
true, //mirrorY
|
||||
true //invertColor
|
||||
);
|
||||
|
||||
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 TDECK_LCD_SPI_HOST SPI2_HOST
|
||||
#define TDECK_LCD_PIN_CS GPIO_NUM_38
|
||||
#define TDECK_LCD_PIN_DC GPIO_NUM_37 // RS
|
||||
#define TDECK_LCD_HORIZONTAL_RESOLUTION 222
|
||||
#define TDECK_LCD_VERTICAL_RESOLUTION 480
|
||||
#define TDECK_LCD_SPI_TRANSFER_HEIGHT (TDECK_LCD_VERTICAL_RESOLUTION / 10)
|
||||
336
Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp
Normal file
336
Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp
Normal file
@ -0,0 +1,336 @@
|
||||
#include "TpagerKeyboard.h"
|
||||
#include <Tactility/hal/i2c/I2c.h>
|
||||
#include <driver/i2c.h>
|
||||
|
||||
#include "freertos/queue.h"
|
||||
#include "driver/gpio.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 },
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(pcnt_new_unit(&unit_config, &encPcntUnit));
|
||||
|
||||
pcnt_glitch_filter_config_t filter_config = {
|
||||
.max_glitch_ns = 1000,
|
||||
};
|
||||
ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(encPcntUnit, &filter_config));
|
||||
|
||||
pcnt_chan_config_t chan_a_config = {
|
||||
.edge_gpio_num = ENCODER_A,
|
||||
.level_gpio_num = ENCODER_B,
|
||||
};
|
||||
pcnt_channel_handle_t pcnt_chan_a = NULL;
|
||||
ESP_ERROR_CHECK(pcnt_new_channel(encPcntUnit, &chan_a_config, &pcnt_chan_a));
|
||||
pcnt_chan_config_t chan_b_config = {
|
||||
.edge_gpio_num = ENCODER_B,
|
||||
.level_gpio_num = ENCODER_A,
|
||||
};
|
||||
pcnt_channel_handle_t pcnt_chan_b = NULL;
|
||||
ESP_ERROR_CHECK(pcnt_new_channel(encPcntUnit, &chan_b_config, &pcnt_chan_b));
|
||||
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan_a, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE));
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan_a, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan_b, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_DECREASE));
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan_b, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
|
||||
|
||||
ESP_ERROR_CHECK(pcnt_unit_add_watch_point(encPcntUnit, low_limit));
|
||||
ESP_ERROR_CHECK(pcnt_unit_add_watch_point(encPcntUnit, high_limit));
|
||||
|
||||
ESP_ERROR_CHECK(pcnt_unit_enable(encPcntUnit));
|
||||
ESP_ERROR_CHECK(pcnt_unit_clear_count(encPcntUnit));
|
||||
ESP_ERROR_CHECK(pcnt_unit_start(encPcntUnit));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
52
Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.h
Normal file
52
Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.h
Normal file
@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/hal/keyboard/KeyboardDevice.h>
|
||||
#include <Tactility/TactilityCore.h>
|
||||
|
||||
#include <Tca8418.h>
|
||||
#include <driver/pulse_cnt.h>
|
||||
#include <driver/ledc.h>
|
||||
#include <driver/gpio.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();
|
||||
91
Boards/LilygoTLoraPager/Source/hal/TpagerPower.cpp
Normal file
91
Boards/LilygoTLoraPager/Source/hal/TpagerPower.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
25
Boards/LilygoTLoraPager/Source/hal/TpagerPower.h
Normal file
25
Boards/LilygoTLoraPager/Source/hal/TpagerPower.h
Normal file
@ -0,0 +1,25 @@
|
||||
#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();
|
||||
33
Boards/LilygoTLoraPager/Source/hal/TpagerSdCard.cpp
Normal file
33
Boards/LilygoTLoraPager/Source/hal/TpagerSdCard.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include "TpagerSdCard.h"
|
||||
|
||||
#include <Tactility/lvgl/LvglSync.h>
|
||||
#include <Tactility/hal/sdcard/SpiSdCardDevice.h>
|
||||
|
||||
#include <esp_vfs_fat.h>
|
||||
|
||||
using tt::hal::sdcard::SpiSdCardDevice;
|
||||
|
||||
#define TDECK_SDCARD_PIN_CS GPIO_NUM_21
|
||||
#define TDECK_LCD_PIN_CS GPIO_NUM_38
|
||||
#define TDECK_RADIO_PIN_CS GPIO_NUM_36
|
||||
|
||||
std::shared_ptr<SdCardDevice> createTpagerSdCard() {
|
||||
auto* configuration = new SpiSdCardDevice::Config(
|
||||
TDECK_SDCARD_PIN_CS,
|
||||
GPIO_NUM_NC,
|
||||
GPIO_NUM_NC,
|
||||
GPIO_NUM_NC,
|
||||
SdCardDevice::MountBehaviour::AtBoot,
|
||||
tt::lvgl::getSyncLock(),
|
||||
{
|
||||
TDECK_RADIO_PIN_CS,
|
||||
TDECK_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();
|
||||
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
|
||||
)
|
||||
5
Drivers/BQ27220/README.md
Normal file
5
Drivers/BQ27220/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# BQ24295
|
||||
|
||||
Power management: I2C-controlled 3A single cell USB charger with narrow VDC 4.5-5.5V adjustable voltage at 1.5A synchronous boost operation.
|
||||
|
||||
[Datasheet](https://www.ti.com/lit/ds/symlink/bq24295.pdf)
|
||||
372
Drivers/BQ27220/Source/Bq27220.cpp
Normal file
372
Drivers/BQ27220/Source/Bq27220.cpp
Normal file
@ -0,0 +1,372 @@
|
||||
#include "Bq27220.h"
|
||||
#include <Tactility/Log.h>
|
||||
|
||||
#include "esp_sleep.h"
|
||||
|
||||
#define TAG "bq27220"
|
||||
|
||||
#define ARRAYSIZE(a) (sizeof(a) / sizeof(*(a)))
|
||||
|
||||
uint8_t highByte(const uint16_t word) { return (word >> 8) & 0xFF; }
|
||||
uint8_t lowByte(const uint16_t word) { return word & 0xFF; }
|
||||
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;
|
||||
} else {
|
||||
TT_LOG_I(TAG, "Full access theoretically.");
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
103
Drivers/BQ27220/Source/Bq27220.h
Normal file
103
Drivers/BQ27220/Source/Bq27220.h
Normal file
@ -0,0 +1,103 @@
|
||||
#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:
|
||||
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 @@
|
||||
# ST7789
|
||||
|
||||
A basic ESP32 LVGL driver for ST7789 displays.
|
||||
217
Drivers/ST7796/Source/St7796Display.cpp
Normal file
217
Drivers/ST7796/Source/St7796Display.cpp
Normal file
@ -0,0 +1,217 @@
|
||||
#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, 0, 49) != 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");
|
||||
}*/
|
||||
}
|
||||
|
||||
98
Drivers/ST7796/Source/St7796Display.h
Normal file
98
Drivers/ST7796/Source/St7796Display.h
Normal file
@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tactility/hal/display/DisplayDevice.h"
|
||||
|
||||
#include <driver/spi_common.h>
|
||||
#include <driver/gpio.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,
|
||||
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),
|
||||
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;
|
||||
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
|
||||
)
|
||||
5
Drivers/TCA8418/README.md
Normal file
5
Drivers/TCA8418/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# BQ24295
|
||||
|
||||
Power management: I2C-controlled 3A single cell USB charger with narrow VDC 4.5-5.5V adjustable voltage at 1.5A synchronous boost operation.
|
||||
|
||||
[Datasheet](https://www.ti.com/lit/ds/symlink/bq24295.pdf)
|
||||
203
Drivers/TCA8418/Source/Tca8418.cpp
Normal file
203
Drivers/TCA8418/Source/Tca8418.cpp
Normal file
@ -0,0 +1,203 @@
|
||||
#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;
|
||||
}
|
||||
67
Drivers/TCA8418/Source/Tca8418.h
Normal file
67
Drivers/TCA8418/Source/Tca8418.h
Normal file
@ -0,0 +1,67 @@
|
||||
#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);
|
||||
};
|
||||
14
Libraries/esp_lcd_st7796/CMakeLists.txt
Normal file
14
Libraries/esp_lcd_st7796/CMakeLists.txt
Normal file
@ -0,0 +1,14 @@
|
||||
set(srcs "esp_lcd_st7796.c"
|
||||
"esp_lcd_st7796_general.c")
|
||||
if(CONFIG_SOC_MIPI_DSI_SUPPORTED)
|
||||
list(APPEND srcs "esp_lcd_st7796_mipi.c")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS "priv_include"
|
||||
REQUIRES "esp_lcd"
|
||||
PRIV_REQUIRES "driver")
|
||||
|
||||
include(package_manager)
|
||||
cu_pkg_define_version(${CMAKE_CURRENT_LIST_DIR})
|
||||
133
Libraries/esp_lcd_st7796/README.md
Normal file
133
Libraries/esp_lcd_st7796/README.md
Normal file
@ -0,0 +1,133 @@
|
||||
# ESP LCD ST7796
|
||||
|
||||
[](https://components.espressif.com/components/espressif/esp_lcd_st7796)
|
||||
|
||||
Implementation of the ST7796 LCD controller with esp_lcd component.
|
||||
|
||||
| LCD controller | Communication interface | Component name | Link to datasheet |
|
||||
| :------------: | :---------------------: | :------------: | :---------------: |
|
||||
| ST7796 | SPI/I80/MIPI-DSI | esp_lcd_st7796 | [Specification](https://www.displayfuture.com/Display/datasheet/controller/ST7796s.pdf) |
|
||||
|
||||
## Add to project
|
||||
|
||||
Packages from this repository are uploaded to [Espressif's component service](https://components.espressif.com/).
|
||||
You can add them to your project via `idf.py add-dependency`, e.g.
|
||||
|
||||
```bash
|
||||
compote manifest add-dependency espressif/esp_lcd_st7796==1.0.0
|
||||
```
|
||||
|
||||
Alternatively, you can create `idf_component.yml`. More is in [Espressif's documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html).
|
||||
|
||||
## Initialization Code
|
||||
|
||||
### I80 interface
|
||||
|
||||
```c
|
||||
ESP_LOGI(TAG, "Initialize Intel 8080 bus");
|
||||
esp_lcd_i80_bus_handle_t i80_bus = NULL;
|
||||
esp_lcd_i80_bus_config_t bus_config = ST7796_PANEL_BUS_I80_CONFIG(
|
||||
EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * EXAMPLE_LCD_BIT_PER_PIXEL / 8, EXAMPLE_LCD_DATA_WIDTH,
|
||||
EXAMPLE_PIN_NUM_LCD_DC, EXAMPLE_PIN_NUM_LCD_WR,
|
||||
EXAMPLE_PIN_NUM_LCD_DATA0, EXAMPLE_PIN_NUM_LCD_DATA1, EXAMPLE_PIN_NUM_LCD_DATA2, EXAMPLE_PIN_NUM_LCD_DATA3,
|
||||
EXAMPLE_PIN_NUM_LCD_DATA4, EXAMPLE_PIN_NUM_LCD_DATA5, EXAMPLE_PIN_NUM_LCD_DATA6, EXAMPLE_PIN_NUM_LCD_DATA7,
|
||||
EXAMPLE_PIN_NUM_LCD_DATA8, EXAMPLE_PIN_NUM_LCD_DATA9, EXAMPLE_PIN_NUM_LCD_DATA10, EXAMPLE_PIN_NUM_LCD_DATA11,
|
||||
EXAMPLE_PIN_NUM_LCD_DATA12, EXAMPLE_PIN_NUM_LCD_DATA13, EXAMPLE_PIN_NUM_LCD_DATA14, EXAMPLE_PIN_NUM_LCD_DATA15);
|
||||
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
|
||||
|
||||
ESP_LOGI(TAG, "Install panel IO");
|
||||
esp_lcd_panel_io_handle_t io_handle = NULL;
|
||||
esp_lcd_panel_io_i80_config_t io_config = ST7796_PANEL_IO_I80_CONFIG(EXAMPLE_PIN_NUM_LCD_CS, example_callback, &example_callback_ctx);
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle));
|
||||
|
||||
/**
|
||||
* Uncomment these lines if use custom initialization commands.
|
||||
* The array should be declared as "static const" and positioned outside the function.
|
||||
*/
|
||||
// static const st7796_lcd_init_cmd_t lcd_init_cmds[] = {
|
||||
// // {cmd, { data }, data_size, delay_ms}
|
||||
// {0xf0, (uint8_t []){0xc3}, 1, 0},
|
||||
// {0xf0, (uint8_t []){0x96}, 1, 0},
|
||||
// {0xb4, (uint8_t []){0x01}, 1, 0},
|
||||
// ...
|
||||
// };
|
||||
|
||||
ESP_LOGI(TAG, "Install ST7796 panel driver");
|
||||
esp_lcd_panel_handle_t panel_handle = NULL;
|
||||
// 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 = EXAMPLE_PIN_NUM_LCD_RST, // Set to -1 if not use
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) // Implemented by LCD command `36h`
|
||||
.color_space = ESP_LCD_COLOR_SPACE_RGB,
|
||||
#else
|
||||
.rgb_endian = LCD_RGB_ENDIAN_RGB,
|
||||
#endif
|
||||
.bits_per_pixel = EXAMPLE_LCD_BIT_PER_PIXEL, // Implemented by LCD command `3Ah` (16/18/24)
|
||||
// .vendor_config = &vendor_config, // Uncomment this line if use custom initialization commands
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7796(io_handle, &panel_config, &panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_disp_off(panel_handle, false));
|
||||
#else
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
|
||||
#endif
|
||||
```
|
||||
|
||||
### MIPI Interface
|
||||
|
||||
```c
|
||||
/**
|
||||
* Uncomment these line if use custom initialization commands.
|
||||
* The array should be declared as static const and positioned outside the function.
|
||||
*/
|
||||
// static const st7796_lcd_init_cmd_t lcd_init_cmds[] = {
|
||||
// // cmd data data_size delay_ms
|
||||
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x13}, 5, 0},
|
||||
// {0xEF, (uint8_t []){0x08}, 1, 0},
|
||||
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0},
|
||||
// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0},
|
||||
// ...
|
||||
// };
|
||||
ESP_LOGI(TAG, "MIPI DSI PHY Powered on");
|
||||
esp_ldo_channel_config_t ldo_mipi_phy_config = {
|
||||
.chan_id = EXAMPLE_MIPI_DSI_PHY_PWR_LDO_CHAN,
|
||||
.voltage_mv = EXAMPLE_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy));
|
||||
|
||||
ESP_LOGI(TAG, "Initialize MIPI DSI bus");
|
||||
esp_lcd_dsi_bus_config_t bus_config = ST7796_PANEL_BUS_DSI_1CH_CONFIG();
|
||||
ESP_ERROR_CHECK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus));
|
||||
|
||||
ESP_LOGI(TAG, "Install panel IO");
|
||||
esp_lcd_dbi_io_config_t dbi_config = ST7796_PANEL_IO_DBI_CONFIG();
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &mipi_dbi_io));
|
||||
|
||||
ESP_LOGI(TAG, "Install LCD driver of st7796");
|
||||
esp_lcd_panel_handle_t panel_handle = NULL;
|
||||
esp_lcd_dpi_panel_config_t dpi_config = ST7796_320_480_PANEL_60HZ_DPI_CONFIG(EXAMPLE_MIPI_DPI_PX_FORMAT);
|
||||
st7796_vendor_config_t vendor_config = {
|
||||
// .init_cmds = lcd_init_cmds, // Uncomment these line if use custom initialization commands
|
||||
// .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(st7796_lcd_init_cmd_t),
|
||||
.flags.use_mipi_interface = 1,
|
||||
.mipi_config = {
|
||||
.dsi_bus = mipi_dsi_bus,
|
||||
.dpi_config = &dpi_config,
|
||||
},
|
||||
};
|
||||
const esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.bits_per_pixel = EXAMPLE_LCD_BIT_PER_PIXEL,
|
||||
.vendor_config = &vendor_config,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7796(mipi_dbi_io, &panel_config, &panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
|
||||
```
|
||||
36
Libraries/esp_lcd_st7796/esp_lcd_st7796.c
Normal file
36
Libraries/esp_lcd_st7796/esp_lcd_st7796.c
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_lcd_types.h"
|
||||
|
||||
#include "esp_lcd_st7796.h"
|
||||
#include "esp_lcd_st7796_interface.h"
|
||||
|
||||
static const char *TAG = "st7796";
|
||||
|
||||
esp_err_t esp_lcd_new_panel_st7796(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
|
||||
esp_lcd_panel_handle_t *ret_panel)
|
||||
{
|
||||
ESP_LOGI(TAG, "version: %d.%d.%d", ESP_LCD_ST7796_VER_MAJOR, ESP_LCD_ST7796_VER_MINOR, ESP_LCD_ST7796_VER_PATCH);
|
||||
ESP_RETURN_ON_FALSE(panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
|
||||
st7796_vendor_config_t *vendor_config = (st7796_vendor_config_t *)panel_dev_config->vendor_config;
|
||||
|
||||
esp_err_t ret = ESP_ERR_NOT_SUPPORTED;
|
||||
|
||||
if (vendor_config && vendor_config->flags.use_mipi_interface) {
|
||||
#if SOC_MIPI_DSI_SUPPORTED
|
||||
ret = esp_lcd_new_panel_st7796_mipi(io, panel_dev_config, ret_panel);
|
||||
#else
|
||||
ESP_LOGE(TAG, "The chip does not support MIPI-DSI interface");
|
||||
#endif
|
||||
} else {
|
||||
ret = esp_lcd_new_panel_st7796_general(io, panel_dev_config, ret_panel);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
354
Libraries/esp_lcd_st7796/esp_lcd_st7796_general.c
Normal file
354
Libraries/esp_lcd_st7796/esp_lcd_st7796_general.c
Normal file
@ -0,0 +1,354 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_lcd_panel_interface.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
#include "esp_lcd_panel_vendor.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_panel_commands.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
|
||||
#include "esp_lcd_st7796.h"
|
||||
#include "esp_lcd_st7796_interface.h"
|
||||
|
||||
static const char *TAG = "st7796_general";
|
||||
|
||||
static esp_err_t panel_st7796_del(esp_lcd_panel_t *panel);
|
||||
static esp_err_t panel_st7796_reset(esp_lcd_panel_t *panel);
|
||||
static esp_err_t panel_st7796_init(esp_lcd_panel_t *panel);
|
||||
static esp_err_t panel_st7796_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
|
||||
static esp_err_t panel_st7796_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
|
||||
static esp_err_t panel_st7796_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
|
||||
static esp_err_t panel_st7796_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
|
||||
static esp_err_t panel_st7796_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
|
||||
static esp_err_t panel_st7796_disp_on_off(esp_lcd_panel_t *panel, bool off);
|
||||
|
||||
typedef struct {
|
||||
esp_lcd_panel_t base;
|
||||
esp_lcd_panel_io_handle_t io;
|
||||
int reset_gpio_num;
|
||||
bool reset_level;
|
||||
int x_gap;
|
||||
int y_gap;
|
||||
uint8_t fb_bits_per_pixel;
|
||||
uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register
|
||||
uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register
|
||||
const st7796_lcd_init_cmd_t *init_cmds;
|
||||
uint16_t init_cmds_size;
|
||||
} st7796_panel_t;
|
||||
|
||||
esp_err_t esp_lcd_new_panel_st7796_general(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
st7796_panel_t *st7796 = NULL;
|
||||
gpio_config_t io_conf = { 0 };
|
||||
|
||||
ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
st7796 = (st7796_panel_t *)calloc(1, sizeof(st7796_panel_t));
|
||||
ESP_GOTO_ON_FALSE(st7796, ESP_ERR_NO_MEM, err, TAG, "no mem for st7796 panel");
|
||||
|
||||
if (panel_dev_config->reset_gpio_num >= 0) {
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num;
|
||||
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
switch (panel_dev_config->color_space) {
|
||||
case ESP_LCD_COLOR_SPACE_RGB:
|
||||
st7796->madctl_val = 0;
|
||||
break;
|
||||
case ESP_LCD_COLOR_SPACE_BGR:
|
||||
st7796->madctl_val |= LCD_CMD_BGR_BIT;
|
||||
break;
|
||||
default:
|
||||
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space");
|
||||
break;
|
||||
}
|
||||
#else
|
||||
switch (panel_dev_config->rgb_endian) {
|
||||
case LCD_RGB_ENDIAN_RGB:
|
||||
st7796->madctl_val = 0;
|
||||
break;
|
||||
case LCD_RGB_ENDIAN_BGR:
|
||||
st7796->madctl_val |= LCD_CMD_BGR_BIT;
|
||||
break;
|
||||
default:
|
||||
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb endian");
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (panel_dev_config->bits_per_pixel) {
|
||||
case 16: // RGB565
|
||||
st7796->colmod_val = 0x05;
|
||||
st7796->fb_bits_per_pixel = 16;
|
||||
break;
|
||||
case 18: // RGB666
|
||||
st7796->colmod_val = 0x06;
|
||||
// each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel
|
||||
st7796->fb_bits_per_pixel = 24;
|
||||
break;
|
||||
case 24: // RGB888
|
||||
st7796->colmod_val = 0x07;
|
||||
st7796->fb_bits_per_pixel = 24;
|
||||
break;
|
||||
default:
|
||||
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
|
||||
break;
|
||||
}
|
||||
|
||||
st7796->io = io;
|
||||
st7796->reset_gpio_num = panel_dev_config->reset_gpio_num;
|
||||
st7796->reset_level = panel_dev_config->flags.reset_active_high;
|
||||
if (panel_dev_config->vendor_config) {
|
||||
st7796->init_cmds = ((st7796_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds;
|
||||
st7796->init_cmds_size = ((st7796_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size;
|
||||
}
|
||||
st7796->base.del = panel_st7796_del;
|
||||
st7796->base.reset = panel_st7796_reset;
|
||||
st7796->base.init = panel_st7796_init;
|
||||
st7796->base.draw_bitmap = panel_st7796_draw_bitmap;
|
||||
st7796->base.invert_color = panel_st7796_invert_color;
|
||||
st7796->base.set_gap = panel_st7796_set_gap;
|
||||
st7796->base.mirror = panel_st7796_mirror;
|
||||
st7796->base.swap_xy = panel_st7796_swap_xy;
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
st7796->base.disp_off = panel_st7796_disp_on_off;
|
||||
#else
|
||||
st7796->base.disp_on_off = panel_st7796_disp_on_off;
|
||||
#endif
|
||||
*ret_panel = &(st7796->base);
|
||||
ESP_LOGD(TAG, "new st7796 panel @%p", st7796);
|
||||
|
||||
ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_ST7796_VER_MAJOR, ESP_LCD_ST7796_VER_MINOR,
|
||||
ESP_LCD_ST7796_VER_PATCH);
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (st7796) {
|
||||
if (panel_dev_config->reset_gpio_num >= 0) {
|
||||
gpio_reset_pin(panel_dev_config->reset_gpio_num);
|
||||
}
|
||||
free(st7796);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7796_del(esp_lcd_panel_t *panel)
|
||||
{
|
||||
st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base);
|
||||
|
||||
if (st7796->reset_gpio_num >= 0) {
|
||||
gpio_reset_pin(st7796->reset_gpio_num);
|
||||
}
|
||||
ESP_LOGD(TAG, "del st7796 panel @%p", st7796);
|
||||
free(st7796);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7796_reset(esp_lcd_panel_t *panel)
|
||||
{
|
||||
st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base);
|
||||
esp_lcd_panel_io_handle_t io = st7796->io;
|
||||
|
||||
// perform hardware reset
|
||||
if (st7796->reset_gpio_num >= 0) {
|
||||
gpio_set_level(st7796->reset_gpio_num, st7796->reset_level);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
gpio_set_level(st7796->reset_gpio_num, !st7796->reset_level);
|
||||
vTaskDelay(pdMS_TO_TICKS(120));
|
||||
} else { // perform software reset
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed");
|
||||
vTaskDelay(pdMS_TO_TICKS(120)); // spec, wait at least 5ms before sending new command
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static const st7796_lcd_init_cmd_t vendor_specific_init_default[] = {
|
||||
// {cmd, { data }, data_size, delay_ms}
|
||||
{0xf0, (uint8_t []){0xc3}, 1, 0},
|
||||
{0xf0, (uint8_t []){0x96}, 1, 0},
|
||||
{0xb4, (uint8_t []){0x01}, 1, 0},
|
||||
{0xb7, (uint8_t []){0xc6}, 1, 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}, 14, 0},
|
||||
{0xe1, (uint8_t []){0xf0, 0x09, 0x0b, 0x06, 0x04, 0x03, 0x2d, 0x43, 0x42, 0x3b, 0x16, 0x14, 0x17, 0x1b}, 14, 0},
|
||||
{0xf0, (uint8_t []){0x3c}, 1, 0},
|
||||
{0xf0, (uint8_t []){0x69}, 1, 0},
|
||||
};
|
||||
|
||||
static esp_err_t panel_st7796_init(esp_lcd_panel_t *panel)
|
||||
{
|
||||
st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base);
|
||||
esp_lcd_panel_io_handle_t io = st7796->io;
|
||||
|
||||
// LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed");
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
|
||||
st7796->madctl_val,
|
||||
}, 1), TAG, "send command failed");
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]) {
|
||||
st7796->colmod_val,
|
||||
}, 1), TAG, "send command failed");
|
||||
|
||||
const st7796_lcd_init_cmd_t *init_cmds = NULL;
|
||||
uint16_t init_cmds_size = 0;
|
||||
if (st7796->init_cmds) {
|
||||
init_cmds = st7796->init_cmds;
|
||||
init_cmds_size = st7796->init_cmds_size;
|
||||
} else {
|
||||
init_cmds = vendor_specific_init_default;
|
||||
init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(st7796_lcd_init_cmd_t);
|
||||
}
|
||||
|
||||
bool is_cmd_overwritten = false;
|
||||
for (int i = 0; i < init_cmds_size; i++) {
|
||||
// Check if the command has been used or conflicts with the internal
|
||||
switch (init_cmds[i].cmd) {
|
||||
case LCD_CMD_MADCTL:
|
||||
is_cmd_overwritten = true;
|
||||
st7796->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
|
||||
break;
|
||||
case LCD_CMD_COLMOD:
|
||||
is_cmd_overwritten = true;
|
||||
st7796->colmod_val = ((uint8_t *)init_cmds[i].data)[0];
|
||||
break;
|
||||
default:
|
||||
is_cmd_overwritten = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_cmd_overwritten) {
|
||||
ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd);
|
||||
}
|
||||
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed");
|
||||
vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms));
|
||||
}
|
||||
ESP_LOGD(TAG, "send init commands success");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7796_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
|
||||
{
|
||||
st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base);
|
||||
assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position");
|
||||
esp_lcd_panel_io_handle_t io = st7796->io;
|
||||
|
||||
x_start += st7796->x_gap;
|
||||
x_end += st7796->x_gap;
|
||||
y_start += st7796->y_gap;
|
||||
y_end += st7796->y_gap;
|
||||
|
||||
// define an area of frame memory where MCU can access
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]) {
|
||||
(x_start >> 8) & 0xFF,
|
||||
x_start & 0xFF,
|
||||
((x_end - 1) >> 8) & 0xFF,
|
||||
(x_end - 1) & 0xFF,
|
||||
}, 4), TAG, "send command failed");
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]) {
|
||||
(y_start >> 8) & 0xFF,
|
||||
y_start & 0xFF,
|
||||
((y_end - 1) >> 8) & 0xFF,
|
||||
(y_end - 1) & 0xFF,
|
||||
}, 4), TAG, "send command failed");
|
||||
// transfer frame buffer
|
||||
size_t len = (x_end - x_start) * (y_end - y_start) * st7796->fb_bits_per_pixel / 8;
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len), TAG, "send command failed");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7796_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
|
||||
{
|
||||
st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base);
|
||||
esp_lcd_panel_io_handle_t io = st7796->io;
|
||||
int command = 0;
|
||||
if (invert_color_data) {
|
||||
command = LCD_CMD_INVON;
|
||||
} else {
|
||||
command = LCD_CMD_INVOFF;
|
||||
}
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7796_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
|
||||
{
|
||||
st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base);
|
||||
esp_lcd_panel_io_handle_t io = st7796->io;
|
||||
if (mirror_x) {
|
||||
st7796->madctl_val |= LCD_CMD_MX_BIT;
|
||||
} else {
|
||||
st7796->madctl_val &= ~LCD_CMD_MX_BIT;
|
||||
}
|
||||
if (mirror_y) {
|
||||
st7796->madctl_val |= LCD_CMD_MY_BIT;
|
||||
} else {
|
||||
st7796->madctl_val &= ~LCD_CMD_MY_BIT;
|
||||
}
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
|
||||
st7796->madctl_val
|
||||
}, 1), TAG, "send command failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7796_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
|
||||
{
|
||||
st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base);
|
||||
esp_lcd_panel_io_handle_t io = st7796->io;
|
||||
if (swap_axes) {
|
||||
st7796->madctl_val |= LCD_CMD_MV_BIT;
|
||||
} else {
|
||||
st7796->madctl_val &= ~LCD_CMD_MV_BIT;
|
||||
}
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
|
||||
st7796->madctl_val
|
||||
}, 1), TAG, "send command failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7796_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
|
||||
{
|
||||
st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base);
|
||||
st7796->x_gap = x_gap;
|
||||
st7796->y_gap = y_gap;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7796_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
|
||||
{
|
||||
st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base);
|
||||
esp_lcd_panel_io_handle_t io = st7796->io;
|
||||
int command = 0;
|
||||
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
on_off = !on_off;
|
||||
#endif
|
||||
|
||||
if (on_off) {
|
||||
command = LCD_CMD_DISPON;
|
||||
} else {
|
||||
command = LCD_CMD_DISPOFF;
|
||||
}
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
296
Libraries/esp_lcd_st7796/esp_lcd_st7796_mipi.c
Normal file
296
Libraries/esp_lcd_st7796/esp_lcd_st7796_mipi.c
Normal file
@ -0,0 +1,296 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#if SOC_MIPI_DSI_SUPPORTED
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_lcd_panel_commands.h"
|
||||
#include "esp_lcd_panel_interface.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
#include "esp_lcd_mipi_dsi.h"
|
||||
#include "esp_lcd_panel_vendor.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
#include "esp_lcd_st7796.h"
|
||||
#include "esp_lcd_st7796_interface.h"
|
||||
|
||||
typedef struct {
|
||||
esp_lcd_panel_io_handle_t io;
|
||||
int reset_gpio_num;
|
||||
uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register
|
||||
uint8_t colmod_val; // save surrent value of LCD_CMD_COLMOD register
|
||||
const st7796_lcd_init_cmd_t *init_cmds;
|
||||
uint16_t init_cmds_size;
|
||||
struct {
|
||||
unsigned int reset_level: 1;
|
||||
} flags;
|
||||
// To save the original functions of MIPI DPI panel
|
||||
esp_err_t (*del)(esp_lcd_panel_t *panel);
|
||||
esp_err_t (*init)(esp_lcd_panel_t *panel);
|
||||
} st7796_panel_t;
|
||||
|
||||
static const char *TAG = "st7796_mipi";
|
||||
|
||||
static esp_err_t panel_st7796_del(esp_lcd_panel_t *panel);
|
||||
static esp_err_t panel_st7796_init(esp_lcd_panel_t *panel);
|
||||
static esp_err_t panel_st7796_reset(esp_lcd_panel_t *panel);
|
||||
static esp_err_t panel_st7796_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
|
||||
static esp_err_t panel_st7796_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
|
||||
static esp_err_t panel_st7796_disp_on_off(esp_lcd_panel_t *panel, bool on_off);
|
||||
|
||||
esp_err_t esp_lcd_new_panel_st7796_mipi(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
|
||||
esp_lcd_panel_handle_t *ret_panel)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments");
|
||||
st7796_vendor_config_t *vendor_config = (st7796_vendor_config_t *)panel_dev_config->vendor_config;
|
||||
ESP_RETURN_ON_FALSE(vendor_config && vendor_config->mipi_config.dpi_config && vendor_config->mipi_config.dsi_bus, ESP_ERR_INVALID_ARG, TAG,
|
||||
"invalid vendor config");
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
st7796_panel_t *st7796 = (st7796_panel_t *)calloc(1, sizeof(st7796_panel_t));
|
||||
ESP_RETURN_ON_FALSE(st7796, ESP_ERR_NO_MEM, TAG, "no mem for st7796 panel");
|
||||
|
||||
if (panel_dev_config->reset_gpio_num >= 0) {
|
||||
gpio_config_t io_conf = {
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
|
||||
}
|
||||
|
||||
switch (panel_dev_config->color_space) {
|
||||
case LCD_RGB_ELEMENT_ORDER_RGB:
|
||||
st7796->madctl_val = 0;
|
||||
break;
|
||||
case LCD_RGB_ELEMENT_ORDER_BGR:
|
||||
st7796->madctl_val |= LCD_CMD_BGR_BIT;
|
||||
break;
|
||||
default:
|
||||
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (panel_dev_config->bits_per_pixel) {
|
||||
case 16: // RGB565
|
||||
st7796->colmod_val = 0x55;
|
||||
break;
|
||||
case 18: // RGB666
|
||||
st7796->colmod_val = 0x66;
|
||||
break;
|
||||
case 24: // RGB888
|
||||
st7796->colmod_val = 0x77;
|
||||
break;
|
||||
default:
|
||||
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
|
||||
break;
|
||||
}
|
||||
|
||||
st7796->io = io;
|
||||
st7796->init_cmds = vendor_config->init_cmds;
|
||||
st7796->init_cmds_size = vendor_config->init_cmds_size;
|
||||
st7796->reset_gpio_num = panel_dev_config->reset_gpio_num;
|
||||
st7796->flags.reset_level = panel_dev_config->flags.reset_active_high;
|
||||
|
||||
// Create MIPI DPI panel
|
||||
esp_lcd_panel_handle_t panel_handle = NULL;
|
||||
ESP_GOTO_ON_ERROR(esp_lcd_new_panel_dpi(vendor_config->mipi_config.dsi_bus, vendor_config->mipi_config.dpi_config, &panel_handle), err, TAG,
|
||||
"create MIPI DPI panel failed");
|
||||
ESP_LOGD(TAG, "new MIPI DPI panel @%p", panel_handle);
|
||||
|
||||
// Save the original functions of MIPI DPI panel
|
||||
st7796->del = panel_handle->del;
|
||||
st7796->init = panel_handle->init;
|
||||
// Overwrite the functions of MIPI DPI panel
|
||||
panel_handle->del = panel_st7796_del;
|
||||
panel_handle->init = panel_st7796_init;
|
||||
panel_handle->reset = panel_st7796_reset;
|
||||
panel_handle->mirror = panel_st7796_mirror;
|
||||
panel_handle->invert_color = panel_st7796_invert_color;
|
||||
panel_handle->disp_on_off = panel_st7796_disp_on_off;
|
||||
panel_handle->user_data = st7796;
|
||||
*ret_panel = panel_handle;
|
||||
ESP_LOGD(TAG, "new st7796 panel @%p", st7796);
|
||||
|
||||
ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_ST7796_VER_MAJOR, ESP_LCD_ST7796_VER_MINOR,
|
||||
ESP_LCD_ST7796_VER_PATCH);
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (st7796) {
|
||||
if (panel_dev_config->reset_gpio_num >= 0) {
|
||||
gpio_reset_pin(panel_dev_config->reset_gpio_num);
|
||||
}
|
||||
free(st7796);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const st7796_lcd_init_cmd_t vendor_specific_init_default[] = {
|
||||
// {cmd, { data }, data_size, delay_ms}
|
||||
{0x11, (uint8_t []){0x00}, 0, 120},
|
||||
{0x36, (uint8_t []){0x48}, 1, 0},
|
||||
{0x3A, (uint8_t []){0x77}, 1, 0},
|
||||
{0xF0, (uint8_t []){0xC3}, 1, 0},
|
||||
{0xF0, (uint8_t []){0x96}, 1, 0},
|
||||
{0xB4, (uint8_t []){0x02}, 1, 0},
|
||||
{0xB7, (uint8_t []){0xC6}, 1, 0},
|
||||
{0xB6, (uint8_t []){0x2F}, 1, 0},
|
||||
{0x11, (uint8_t []){0xC0, 0xF0, 0x35}, 3, 0},
|
||||
{0xC1, (uint8_t []){0x15}, 1, 0},
|
||||
{0xC2, (uint8_t []){0xAF}, 1, 0},
|
||||
{0xC3, (uint8_t []){0x09}, 1, 0},
|
||||
{0xC5, (uint8_t []){0x22}, 1, 0},
|
||||
{0xC6, (uint8_t []){0x00}, 1, 0},
|
||||
{0x11, (uint8_t []){0xE8, 0x40, 0x8A, 0x00, 0x00, 0x29, 0x19, 0xA5, 0x33}, 9, 0},
|
||||
{0x11, (uint8_t []){0xE0, 0x70, 0x00, 0x05, 0x03, 0x02, 0x20, 0x29, 0x01, 0x45, 0x30, 0x09, 0x07, 0x22, 0x29}, 15, 0},
|
||||
{0x11, (uint8_t []){0xE1, 0x70, 0x0C, 0x10, 0x0F, 0x0E, 0x09, 0x35, 0x64, 0x48, 0x3A, 0x14, 0x13, 0x2E, 0x30}, 15, 0},
|
||||
{0x11, (uint8_t []){0xE0, 0x70, 0x04, 0x0A, 0x0B, 0x0A, 0x27, 0x31, 0x55, 0x47, 0x29, 0x13, 0x13, 0x29, 0x2D}, 15, 0},
|
||||
{0x11, (uint8_t []){0xE1, 0x70, 0x08, 0x0E, 0x09, 0x08, 0x04, 0x33, 0x32, 0x49, 0x36, 0x14, 0x14, 0x2A, 0x2F}, 15, 0},
|
||||
{0x21, (uint8_t []){0x00}, 0, 0},
|
||||
{0xF0, (uint8_t []){0xC3}, 1, 0},
|
||||
{0xF0, (uint8_t []){0x96}, 1, 120},
|
||||
{0xF0, (uint8_t []){0xC3}, 1, 0},
|
||||
{0x29, (uint8_t []){0x00}, 0, 0},
|
||||
{0x2C, (uint8_t []){0x00}, 0, 0},
|
||||
//============ Gamma END===========
|
||||
};
|
||||
|
||||
static esp_err_t panel_st7796_del(esp_lcd_panel_t *panel)
|
||||
{
|
||||
st7796_panel_t *st7796 = (st7796_panel_t *)panel->user_data;
|
||||
|
||||
if (st7796->reset_gpio_num >= 0) {
|
||||
gpio_reset_pin(st7796->reset_gpio_num);
|
||||
}
|
||||
// Delete MIPI DPI panel
|
||||
st7796->del(panel);
|
||||
ESP_LOGD(TAG, "del st7796 panel @%p", st7796);
|
||||
free(st7796);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7796_init(esp_lcd_panel_t *panel)
|
||||
{
|
||||
st7796_panel_t *st7796 = (st7796_panel_t *)panel->user_data;
|
||||
esp_lcd_panel_io_handle_t io = st7796->io;
|
||||
const st7796_lcd_init_cmd_t *init_cmds = NULL;
|
||||
uint16_t init_cmds_size = 0;
|
||||
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
|
||||
st7796->madctl_val,
|
||||
}, 1), TAG, "send command failed");
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]) {
|
||||
st7796->colmod_val,
|
||||
}, 1), TAG, "send command failed");
|
||||
|
||||
// vendor specific initialization, it can be different between manufacturers
|
||||
// should consult the LCD supplier for initialization sequence code
|
||||
if (st7796->init_cmds) {
|
||||
init_cmds = st7796->init_cmds;
|
||||
init_cmds_size = st7796->init_cmds_size;
|
||||
} else {
|
||||
init_cmds = vendor_specific_init_default;
|
||||
init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(st7796_lcd_init_cmd_t);
|
||||
}
|
||||
|
||||
for (int i = 0; i < init_cmds_size; i++) {
|
||||
// Send command
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed");
|
||||
vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms));
|
||||
}
|
||||
ESP_LOGD(TAG, "send init commands success");
|
||||
|
||||
ESP_RETURN_ON_ERROR(st7796->init(panel), TAG, "init MIPI DPI panel failed");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7796_reset(esp_lcd_panel_t *panel)
|
||||
{
|
||||
st7796_panel_t *st7796 = (st7796_panel_t *)panel->user_data;
|
||||
esp_lcd_panel_io_handle_t io = st7796->io;
|
||||
|
||||
// Perform hardware reset
|
||||
if (st7796->reset_gpio_num >= 0) {
|
||||
gpio_set_level(st7796->reset_gpio_num, st7796->flags.reset_level);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
gpio_set_level(st7796->reset_gpio_num, !st7796->flags.reset_level);
|
||||
vTaskDelay(pdMS_TO_TICKS(120));
|
||||
} else if (io) { // Perform software reset
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed");
|
||||
vTaskDelay(pdMS_TO_TICKS(120));
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7796_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
|
||||
{
|
||||
st7796_panel_t *st7796 = (st7796_panel_t *)panel->user_data;
|
||||
esp_lcd_panel_io_handle_t io = st7796->io;
|
||||
uint8_t command = 0;
|
||||
|
||||
ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO");
|
||||
|
||||
if (invert_color_data) {
|
||||
command = LCD_CMD_INVON;
|
||||
} else {
|
||||
command = LCD_CMD_INVOFF;
|
||||
}
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7796_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
|
||||
{
|
||||
st7796_panel_t *st7796 = (st7796_panel_t *)panel->user_data;
|
||||
esp_lcd_panel_io_handle_t io = st7796->io;
|
||||
uint8_t madctl_val = st7796->madctl_val;
|
||||
|
||||
ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO");
|
||||
|
||||
// Control mirror through LCD command
|
||||
if (mirror_x) {
|
||||
madctl_val |= LCD_CMD_MX_BIT;
|
||||
} else {
|
||||
madctl_val &= ~LCD_CMD_MX_BIT;
|
||||
}
|
||||
if (mirror_y) {
|
||||
madctl_val |= LCD_CMD_MY_BIT;
|
||||
} else {
|
||||
madctl_val &= ~LCD_CMD_MY_BIT;
|
||||
}
|
||||
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t []) {
|
||||
madctl_val
|
||||
}, 1), TAG, "send command failed");
|
||||
st7796->madctl_val = madctl_val;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7796_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
|
||||
{
|
||||
st7796_panel_t *st7796 = (st7796_panel_t *)panel->user_data;
|
||||
esp_lcd_panel_io_handle_t io = st7796->io;
|
||||
int command = 0;
|
||||
|
||||
if (on_off) {
|
||||
command = LCD_CMD_DISPON;
|
||||
} else {
|
||||
command = LCD_CMD_DISPOFF;
|
||||
}
|
||||
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif
|
||||
15
Libraries/esp_lcd_st7796/idf_component.yml
Normal file
15
Libraries/esp_lcd_st7796/idf_component.yml
Normal file
@ -0,0 +1,15 @@
|
||||
dependencies:
|
||||
cmake_utilities: 0.*
|
||||
idf: '>=4.4'
|
||||
description: ESP LCD ST7796 driver (SPI && I80 && MIPI DSI)
|
||||
repository: git://github.com/espressif/esp-bsp.git
|
||||
repository_info:
|
||||
commit_sha: 7e5759a5dcae75624e0c7abb8d8aef6b95e33b1f
|
||||
path: components/lcd/esp_lcd_st7796
|
||||
targets:
|
||||
- esp32
|
||||
- esp32s2
|
||||
- esp32s3
|
||||
- esp32p4
|
||||
url: https://github.com/espressif/esp-bsp/tree/master/components/lcd/esp_lcd_st7796
|
||||
version: 1.3.2
|
||||
206
Libraries/esp_lcd_st7796/include/esp_lcd_st7796.h
Normal file
206
Libraries/esp_lcd_st7796/include/esp_lcd_st7796.h
Normal file
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/**
|
||||
* @file
|
||||
* @brief ESP LCD: ST7796
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "hal/lcd_types.h"
|
||||
#include "esp_lcd_panel_vendor.h"
|
||||
#include "esp_idf_version.h"
|
||||
#if SOC_MIPI_DSI_SUPPORTED
|
||||
#include "esp_lcd_mipi_dsi.h"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief LCD panel initialization commands.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
int cmd; /*<! The specific LCD command */
|
||||
const void *data; /*<! Buffer that holds the command specific data */
|
||||
size_t data_bytes; /*<! Size of `data` in memory, in bytes */
|
||||
unsigned int delay_ms; /*<! Delay in milliseconds after this command */
|
||||
} st7796_lcd_init_cmd_t;
|
||||
|
||||
/**
|
||||
* @brief LCD panel vendor configuration.
|
||||
*
|
||||
* @note This structure needs to be passed to the `vendor_config` field in `esp_lcd_panel_dev_config_t`.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
const st7796_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array. Set to NULL if using default commands.
|
||||
* The array should be declared as `static const` and positioned outside the function.
|
||||
* Please refer to `vendor_specific_init_default` in source file.
|
||||
*/
|
||||
uint16_t init_cmds_size; /*<! Number of commands in above array */
|
||||
#if SOC_MIPI_DSI_SUPPORTED
|
||||
struct {
|
||||
esp_lcd_dsi_bus_handle_t dsi_bus; /*!< MIPI-DSI bus configuration */
|
||||
const esp_lcd_dpi_panel_config_t *dpi_config; /*!< MIPI-DPI panel configuration */
|
||||
} mipi_config;
|
||||
#endif
|
||||
struct {
|
||||
unsigned int use_mipi_interface: 1; /*<! Set to 1 if using MIPI interface, default is SPI/I80 interface */
|
||||
} flags;
|
||||
} st7796_vendor_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create LCD panel for model ST7796
|
||||
*
|
||||
* @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for initialization sequence code.
|
||||
*
|
||||
* @param[in] io LCD panel IO handle
|
||||
* @param[in] panel_dev_config general panel device configuration
|
||||
* @param[out] ret_panel Returned LCD panel handle
|
||||
* @return
|
||||
* - ESP_ERR_INVALID_ARG if parameter is invalid
|
||||
* - ESP_ERR_NO_MEM if out of memory
|
||||
* - ESP_OK on success
|
||||
*/
|
||||
esp_err_t esp_lcd_new_panel_st7796(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////// Default Configuration Macros for I80 Interface /////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @brief LCD panel bus configuration structure
|
||||
*
|
||||
* @param[in] max_trans_bytes Maximum bytes of data to be transferred in one transaction. Normally set to the size of one frame bytes.
|
||||
* @param[in] data_width Data bus width
|
||||
* @param[in] dc I80 data/command pin number
|
||||
* @param[in] wr I80 write clock pin number
|
||||
* @param[in] d[0:15] I80 data pin number 0 ~ 15. Set to -1 if not used.
|
||||
*
|
||||
*/
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 1)
|
||||
#define ST7796_PANEL_BUS_I80_CONFIG(max_trans_bytes, data_width, dc, wr, d0, d1, d2, d3, d4, \
|
||||
d5, d6, d7, d8 , d9, d10, d11, d12, d13, d14, d15) \
|
||||
{ \
|
||||
.clk_src = LCD_CLK_SRC_PLL160M, \
|
||||
.dc_gpio_num = dc, \
|
||||
.wr_gpio_num = wr, \
|
||||
.data_gpio_nums = { \
|
||||
d0, d1, d2, d3, d4, d5, d6, d7, d8 , d9, d10, d11, d12, d13, d14, d15 \
|
||||
}, \
|
||||
.bus_width = data_width, \
|
||||
.max_transfer_bytes = max_trans_bytes, \
|
||||
}
|
||||
#else
|
||||
#define ST7796_PANEL_BUS_I80_CONFIG(max_trans_bytes, data_width, dc, wr, d0, d1, d2, d3, d4, \
|
||||
d5, d6, d7, d8 , d9, d10, d11, d12, d13, d14, d15) \
|
||||
{ \
|
||||
.clk_src = LCD_CLK_SRC_PLL160M, \
|
||||
.dc_gpio_num = dc, \
|
||||
.wr_gpio_num = wr, \
|
||||
.data_gpio_nums = { \
|
||||
d0, d1, d2, d3, d4, d5, d6, d7, d8 , d9, d10, d11, d12, d13, d14, d15 \
|
||||
}, \
|
||||
.bus_width = data_width, \
|
||||
.max_transfer_bytes = max_trans_bytes, \
|
||||
.psram_trans_align = 64, \
|
||||
.sram_trans_align = 4, \
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief LCD panel IO configuration structure
|
||||
*
|
||||
* @param[in] cs I80 chip select pin number
|
||||
* @param[in] cb Callback function when data transfer is done
|
||||
* @param[in] cb_ctx Callback function context
|
||||
*
|
||||
*/
|
||||
#define ST7796_PANEL_IO_I80_CONFIG(cs, cb, cb_ctx) \
|
||||
{ \
|
||||
.cs_gpio_num = cs, \
|
||||
.pclk_hz = 10 * 1000 * 1000, \
|
||||
.trans_queue_depth = 10, \
|
||||
.dc_levels = { \
|
||||
.dc_idle_level = 0, \
|
||||
.dc_cmd_level = 0, \
|
||||
.dc_dummy_level = 0, \
|
||||
.dc_data_level = 1, \
|
||||
}, \
|
||||
.flags = { \
|
||||
.swap_color_bytes = 1, \
|
||||
}, \
|
||||
.on_color_trans_done = cb, \
|
||||
.user_ctx = cb_ctx, \
|
||||
.lcd_cmd_bits = 8, \
|
||||
.lcd_param_bits = 8, \
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////// Default Configuration Macros for MIPI-DSI Interface //////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @brief MIPI-DSI bus configuration structure
|
||||
*
|
||||
* @param[in] lane_num Number of data lanes
|
||||
* @param[in] lane_mbps Lane bit rate in Mbps
|
||||
*
|
||||
*/
|
||||
#define ST7796_PANEL_BUS_DSI_1CH_CONFIG() \
|
||||
{ \
|
||||
.bus_id = 0, \
|
||||
.num_data_lanes = 1, \
|
||||
.phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, \
|
||||
.lane_bit_rate_mbps = 480, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MIPI-DBI panel IO configuration structure
|
||||
*
|
||||
*/
|
||||
#define ST7796_PANEL_IO_DBI_CONFIG() \
|
||||
{ \
|
||||
.virtual_channel = 0, \
|
||||
.lcd_cmd_bits = 8, \
|
||||
.lcd_param_bits = 8, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MIPI DPI configuration structure
|
||||
*
|
||||
* @note refresh_rate = (dpi_clock_freq_mhz * 1000000) / (h_res + hsync_pulse_width + hsync_back_porch + hsync_front_porch)
|
||||
* / (v_res + vsync_pulse_width + vsync_back_porch + vsync_front_porch)
|
||||
*
|
||||
* @param[in] px_format Pixel format of the panel
|
||||
*
|
||||
*/
|
||||
#define ST7796_320_480_PANEL_60HZ_DPI_CONFIG(px_format) \
|
||||
{ \
|
||||
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, \
|
||||
.dpi_clock_freq_mhz = 10, \
|
||||
.virtual_channel = 0, \
|
||||
.pixel_format = px_format, \
|
||||
.num_fbs = 1, \
|
||||
.video_timing = { \
|
||||
.h_size = 320, \
|
||||
.v_size = 480, \
|
||||
.hsync_back_porch = 10, \
|
||||
.hsync_pulse_width = 10, \
|
||||
.hsync_front_porch = 20, \
|
||||
.vsync_back_porch = 10, \
|
||||
.vsync_pulse_width = 10, \
|
||||
.vsync_front_porch = 10, \
|
||||
}, \
|
||||
.flags.use_dma2d = true, \
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
202
Libraries/esp_lcd_st7796/license.txt
Normal file
202
Libraries/esp_lcd_st7796/license.txt
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_lcd_types.h"
|
||||
#include "esp_lcd_panel_vendor.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
/**
|
||||
* @brief Initialize ST7796 LCD panel with SPI/I80 interface
|
||||
*
|
||||
* @param[in] io LCD panel IO handle
|
||||
* @param[in] panel_dev_config LCD panel device configuration
|
||||
* @param[out] ret_panel LCD panel handle
|
||||
* @return
|
||||
* - ESP_OK: Success
|
||||
* - Otherwise: Fail
|
||||
*/
|
||||
esp_err_t esp_lcd_new_panel_st7796_general(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel);
|
||||
|
||||
#if SOC_MIPI_DSI_SUPPORTED
|
||||
/**
|
||||
* @brief Initialize ST7796 LCD panel with MIPI interface
|
||||
*
|
||||
* @param[in] io LCD panel IO handle
|
||||
* @param[in] panel_dev_config LCD panel device configuration
|
||||
* @param[out] ret_panel LCD panel handle
|
||||
* @return
|
||||
* - ESP_OK: Success
|
||||
* - Otherwise: Fail
|
||||
*/
|
||||
esp_err_t esp_lcd_new_panel_st7796_mipi(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
|
||||
esp_lcd_panel_handle_t *ret_panel);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
6
Libraries/esp_lcd_st7796/test_apps/CMakeLists.txt
Normal file
6
Libraries/esp_lcd_st7796/test_apps/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components")
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(test_esp_lcd_st7796)
|
||||
Binary file not shown.
3
Libraries/esp_lcd_st7796/test_apps/main/CMakeLists.txt
Normal file
3
Libraries/esp_lcd_st7796/test_apps/main/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
idf_component_register(SRC_DIRS "."
|
||||
INCLUDE_DIRS "."
|
||||
WHOLE_ARCHIVE)
|
||||
@ -0,0 +1,6 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
idf: ">=4.4"
|
||||
esp_lcd_st7796:
|
||||
version: "*"
|
||||
override_path: "../../../esp_lcd_st7796"
|
||||
47
Libraries/esp_lcd_st7796/test_apps/main/test_app_main.c
Normal file
47
Libraries/esp_lcd_st7796/test_apps/main/test_app_main.c
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "unity_test_utils_memory.h"
|
||||
#include "esp_heap_caps.h"
|
||||
|
||||
// Some resources are lazy allocated in the LCD driver, the threadhold is left for that case
|
||||
#define TEST_MEMORY_LEAK_THRESHOLD (300)
|
||||
|
||||
static size_t before_free_8bit;
|
||||
static size_t before_free_32bit;
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
|
||||
unity_utils_check_leak(before_free_8bit, after_free_8bit, "8BIT", TEST_MEMORY_LEAK_THRESHOLD);
|
||||
unity_utils_check_leak(before_free_32bit, after_free_32bit, "32BIT", TEST_MEMORY_LEAK_THRESHOLD);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/**
|
||||
* __ _____ _____ _____ ___ __
|
||||
* / _\/__ \___ |___ / _ \ / /_
|
||||
* \ \ / /\/ / / / / (_) | '_ \
|
||||
* _\ \ / / / / / / \__, | (_) |
|
||||
* \__/ \/ /_/ /_/ /_/ \___/
|
||||
*/
|
||||
printf(" __ _____ _____ _____ ___ __\r\n");
|
||||
printf("/ _\\/__ \\___ |___ / _ \\ / /_\r\n");
|
||||
printf("\\ \\ / /\\/ / / / / (_) | '_ \\\r\n");
|
||||
printf("_\\ \\ / / / / / / \\__, | (_) |\r\n");
|
||||
printf("\\__/ \\/ /_/ /_/ /_/ \\___/\r\n");
|
||||
unity_run_menu();
|
||||
}
|
||||
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/soc_caps.h"
|
||||
#if SOC_LCD_I80_SUPPORTED
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
|
||||
#include "esp_lcd_st7796.h"
|
||||
|
||||
#define TEST_LCD_H_RES (320)
|
||||
#define TEST_LCD_V_RES (480)
|
||||
#define TEST_LCD_BIT_PER_PIXEL (16)
|
||||
#define TEST_LCD_DATA_WIDTH (8)
|
||||
|
||||
#define TEST_PIN_NUM_LCD_CS (GPIO_NUM_17)
|
||||
#define TEST_PIN_NUM_LCD_DC (GPIO_NUM_46)
|
||||
#define TEST_PIN_NUM_LCD_WR (GPIO_NUM_3)
|
||||
#define TEST_PIN_NUM_LCD_DATA0 (GPIO_NUM_9)
|
||||
#define TEST_PIN_NUM_LCD_DATA1 (GPIO_NUM_12)
|
||||
#define TEST_PIN_NUM_LCD_DATA2 (GPIO_NUM_11)
|
||||
#define TEST_PIN_NUM_LCD_DATA3 (GPIO_NUM_14)
|
||||
#define TEST_PIN_NUM_LCD_DATA4 (GPIO_NUM_13)
|
||||
#if CONFIG_IDF_TARGET_ESP32S2
|
||||
#define TEST_PIN_NUM_LCD_DATA5 (GPIO_NUM_8)
|
||||
#else
|
||||
#define TEST_PIN_NUM_LCD_DATA5 (GPIO_NUM_47)
|
||||
#endif
|
||||
#define TEST_PIN_NUM_LCD_DATA6 (GPIO_NUM_21)
|
||||
#define TEST_PIN_NUM_LCD_DATA7 (GPIO_NUM_45)
|
||||
#define TEST_PIN_NUM_LCD_DATA8 (-1)
|
||||
#define TEST_PIN_NUM_LCD_DATA9 (-1)
|
||||
#define TEST_PIN_NUM_LCD_DATA10 (-1)
|
||||
#define TEST_PIN_NUM_LCD_DATA11 (-1)
|
||||
#define TEST_PIN_NUM_LCD_DATA12 (-1)
|
||||
#define TEST_PIN_NUM_LCD_DATA13 (-1)
|
||||
#define TEST_PIN_NUM_LCD_DATA14 (-1)
|
||||
#define TEST_PIN_NUM_LCD_DATA15 (-1)
|
||||
#define TEST_PIN_NUM_LCD_RST (GPIO_NUM_NC)
|
||||
|
||||
#define TEST_DELAY_TIME_MS (3000)
|
||||
|
||||
static char *TAG = "st7796_test";
|
||||
static SemaphoreHandle_t refresh_finish = NULL;
|
||||
|
||||
IRAM_ATTR static bool test_notify_refresh_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
BaseType_t need_yield = pdFALSE;
|
||||
|
||||
xSemaphoreGiveFromISR(refresh_finish, &need_yield);
|
||||
return (need_yield == pdTRUE);
|
||||
}
|
||||
|
||||
static void test_draw_bitmap(esp_lcd_panel_handle_t panel_handle)
|
||||
{
|
||||
refresh_finish = xSemaphoreCreateBinary();
|
||||
TEST_ASSERT_NOT_NULL(refresh_finish);
|
||||
|
||||
uint16_t row_line = TEST_LCD_V_RES / TEST_LCD_BIT_PER_PIXEL;
|
||||
uint8_t byte_per_pixel = TEST_LCD_BIT_PER_PIXEL / 8;
|
||||
uint8_t *color = (uint8_t *)heap_caps_calloc(1, row_line * TEST_LCD_H_RES * byte_per_pixel, MALLOC_CAP_DMA);
|
||||
TEST_ASSERT_NOT_NULL(color);
|
||||
|
||||
for (int j = 0; j < TEST_LCD_BIT_PER_PIXEL; j++) {
|
||||
for (int i = 0; i < row_line * TEST_LCD_H_RES; i++) {
|
||||
for (int k = 0; k < byte_per_pixel; k++) {
|
||||
color[i * byte_per_pixel + k] = (BIT(j) >> (k * 8)) & 0xff;
|
||||
}
|
||||
}
|
||||
TEST_ESP_OK(esp_lcd_panel_draw_bitmap(panel_handle, 0, j * row_line, TEST_LCD_H_RES, (j + 1) * row_line, color));
|
||||
xSemaphoreTake(refresh_finish, portMAX_DELAY);
|
||||
}
|
||||
free(color);
|
||||
}
|
||||
|
||||
TEST_CASE("test st7796 to draw color bar with I80 interface", "[st7796][i80]")
|
||||
{
|
||||
ESP_LOGI(TAG, "Initialize Intel 8080 bus");
|
||||
esp_lcd_i80_bus_handle_t i80_bus = NULL;
|
||||
esp_lcd_i80_bus_config_t bus_config = ST7796_PANEL_BUS_I80_CONFIG(
|
||||
TEST_LCD_H_RES * TEST_LCD_V_RES * TEST_LCD_BIT_PER_PIXEL / 8, TEST_LCD_DATA_WIDTH,
|
||||
TEST_PIN_NUM_LCD_DC, TEST_PIN_NUM_LCD_WR,
|
||||
TEST_PIN_NUM_LCD_DATA0, TEST_PIN_NUM_LCD_DATA1, TEST_PIN_NUM_LCD_DATA2, TEST_PIN_NUM_LCD_DATA3,
|
||||
TEST_PIN_NUM_LCD_DATA4, TEST_PIN_NUM_LCD_DATA5, TEST_PIN_NUM_LCD_DATA6, TEST_PIN_NUM_LCD_DATA7,
|
||||
TEST_PIN_NUM_LCD_DATA8, TEST_PIN_NUM_LCD_DATA9, TEST_PIN_NUM_LCD_DATA10, TEST_PIN_NUM_LCD_DATA11,
|
||||
TEST_PIN_NUM_LCD_DATA12, TEST_PIN_NUM_LCD_DATA13, TEST_PIN_NUM_LCD_DATA14, TEST_PIN_NUM_LCD_DATA15);
|
||||
TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
|
||||
|
||||
ESP_LOGI(TAG, "Install panel IO");
|
||||
esp_lcd_panel_io_handle_t io_handle = NULL;
|
||||
esp_lcd_panel_io_i80_config_t io_config = ST7796_PANEL_IO_I80_CONFIG(TEST_PIN_NUM_LCD_CS, test_notify_refresh_ready, NULL);
|
||||
TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle));
|
||||
|
||||
ESP_LOGI(TAG, "Install ST7796 panel driver");
|
||||
const esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = TEST_PIN_NUM_LCD_RST,
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
.color_space = ESP_LCD_COLOR_SPACE_BGR,
|
||||
#else
|
||||
.rgb_endian = LCD_RGB_ENDIAN_BGR,
|
||||
#endif
|
||||
.bits_per_pixel = TEST_LCD_BIT_PER_PIXEL,
|
||||
};
|
||||
esp_lcd_panel_handle_t panel_handle = NULL;
|
||||
TEST_ESP_OK(esp_lcd_new_panel_st7796(io_handle, &panel_config, &panel_handle));
|
||||
TEST_ESP_OK(esp_lcd_panel_reset(panel_handle));
|
||||
TEST_ESP_OK(esp_lcd_panel_init(panel_handle));
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
TEST_ESP_OK(esp_lcd_panel_disp_off(panel_handle, false));
|
||||
#else
|
||||
TEST_ESP_OK(esp_lcd_panel_disp_on_off(panel_handle, true));
|
||||
#endif
|
||||
|
||||
test_draw_bitmap(panel_handle);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_TIME_MS));
|
||||
|
||||
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
|
||||
TEST_ESP_OK(esp_lcd_panel_io_del(io_handle));
|
||||
TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus));
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,225 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#if SOC_MIPI_DSI_SUPPORTED
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
#include "esp_ldo_regulator.h"
|
||||
#include "esp_dma_utils.h"
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "unity_test_utils_memory.h"
|
||||
#include "esp_lcd_mipi_dsi.h"
|
||||
#include "esp_lcd_st7796.h"
|
||||
|
||||
#define TEST_LCD_H_RES (320)
|
||||
#define TEST_LCD_V_RES (480)
|
||||
#define TEST_LCD_BIT_PER_PIXEL (24)
|
||||
#define TEST_PIN_NUM_LCD_RST (-1)
|
||||
#define TEST_PIN_NUM_BK_LIGHT (-1) // set to -1 if not used
|
||||
#define TEST_LCD_BK_LIGHT_ON_LEVEL (1)
|
||||
#define TEST_LCD_BK_LIGHT_OFF_LEVEL !TEST_LCD_BK_LIGHT_ON_LEVEL
|
||||
|
||||
#if TEST_LCD_BIT_PER_PIXEL == 24
|
||||
#define TEST_MIPI_DPI_PX_FORMAT (LCD_COLOR_PIXEL_FORMAT_RGB888)
|
||||
#elif TEST_LCD_BIT_PER_PIXEL == 18
|
||||
#define TEST_MIPI_DPI_PX_FORMAT (LCD_COLOR_PIXEL_FORMAT_RGB666)
|
||||
#elif TEST_LCD_BIT_PER_PIXEL == 16
|
||||
#define TEST_MIPI_DPI_PX_FORMAT (LCD_COLOR_PIXEL_FORMAT_RGB565)
|
||||
#endif
|
||||
|
||||
#define TEST_DELAY_TIME_MS (3000)
|
||||
|
||||
#define TEST_MIPI_DSI_PHY_PWR_LDO_CHAN (3)
|
||||
#define TEST_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500)
|
||||
|
||||
static char *TAG = "st7796_test";
|
||||
static esp_ldo_channel_handle_t ldo_mipi_phy = NULL;
|
||||
static esp_lcd_panel_handle_t panel_handle = NULL;
|
||||
static esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL;
|
||||
static esp_lcd_panel_io_handle_t mipi_dbi_io = NULL;
|
||||
static SemaphoreHandle_t refresh_finish = NULL;
|
||||
|
||||
IRAM_ATTR static bool test_notify_refresh_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
SemaphoreHandle_t refresh_finish = (SemaphoreHandle_t)user_ctx;
|
||||
BaseType_t need_yield = pdFALSE;
|
||||
|
||||
xSemaphoreGiveFromISR(refresh_finish, &need_yield);
|
||||
|
||||
return (need_yield == pdTRUE);
|
||||
}
|
||||
|
||||
static void test_init_lcd(void)
|
||||
{
|
||||
#if TEST_PIN_NUM_BK_LIGHT >= 0
|
||||
ESP_LOGI(TAG, "Turn on LCD backlight");
|
||||
gpio_config_t bk_gpio_config = {
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pin_bit_mask = 1ULL << TEST_PIN_NUM_BK_LIGHT
|
||||
};
|
||||
TEST_ESP_OK(gpio_config(&bk_gpio_config));
|
||||
TEST_ESP_OK(gpio_set_level(TEST_PIN_NUM_BK_LIGHT, TEST_LCD_BK_LIGHT_ON_LEVEL));
|
||||
#endif
|
||||
|
||||
// Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state
|
||||
#ifdef TEST_MIPI_DSI_PHY_PWR_LDO_CHAN
|
||||
ESP_LOGI(TAG, "MIPI DSI PHY Powered on");
|
||||
esp_ldo_channel_config_t ldo_mipi_phy_config = {
|
||||
.chan_id = TEST_MIPI_DSI_PHY_PWR_LDO_CHAN,
|
||||
.voltage_mv = TEST_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
|
||||
};
|
||||
TEST_ESP_OK(esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy));
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "Initialize MIPI DSI bus");
|
||||
esp_lcd_dsi_bus_config_t bus_config = ST7796_PANEL_BUS_DSI_1CH_CONFIG();
|
||||
TEST_ESP_OK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus));
|
||||
|
||||
ESP_LOGI(TAG, "Install panel IO");
|
||||
esp_lcd_dbi_io_config_t dbi_config = ST7796_PANEL_IO_DBI_CONFIG();
|
||||
TEST_ESP_OK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &mipi_dbi_io));
|
||||
|
||||
ESP_LOGI(TAG, "Install LCD driver of st7796");
|
||||
esp_lcd_dpi_panel_config_t dpi_config = ST7796_320_480_PANEL_60HZ_DPI_CONFIG(TEST_MIPI_DPI_PX_FORMAT);
|
||||
st7796_vendor_config_t vendor_config = {
|
||||
.flags.use_mipi_interface = 1,
|
||||
.mipi_config.dsi_bus = mipi_dsi_bus,
|
||||
.mipi_config.dpi_config = &dpi_config,
|
||||
};
|
||||
const esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = TEST_PIN_NUM_LCD_RST,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.bits_per_pixel = TEST_LCD_BIT_PER_PIXEL,
|
||||
.vendor_config = &vendor_config,
|
||||
};
|
||||
TEST_ESP_OK(esp_lcd_new_panel_st7796(mipi_dbi_io, &panel_config, &panel_handle));
|
||||
TEST_ESP_OK(esp_lcd_panel_reset(panel_handle));
|
||||
TEST_ESP_OK(esp_lcd_panel_init(panel_handle));
|
||||
TEST_ESP_OK(esp_lcd_panel_disp_on_off(panel_handle, true));
|
||||
|
||||
refresh_finish = xSemaphoreCreateBinary();
|
||||
TEST_ASSERT_NOT_NULL(refresh_finish);
|
||||
esp_lcd_dpi_panel_event_callbacks_t cbs = {
|
||||
.on_color_trans_done = test_notify_refresh_ready,
|
||||
};
|
||||
TEST_ESP_OK(esp_lcd_dpi_panel_register_event_callbacks(panel_handle, &cbs, refresh_finish));
|
||||
}
|
||||
|
||||
static void test_deinit_lcd(void)
|
||||
{
|
||||
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
|
||||
TEST_ESP_OK(esp_lcd_panel_io_del(mipi_dbi_io));
|
||||
TEST_ESP_OK(esp_lcd_del_dsi_bus(mipi_dsi_bus));
|
||||
panel_handle = NULL;
|
||||
mipi_dbi_io = NULL;
|
||||
mipi_dsi_bus = NULL;
|
||||
|
||||
if (ldo_mipi_phy) {
|
||||
TEST_ESP_OK(esp_ldo_release_channel(ldo_mipi_phy));
|
||||
ldo_mipi_phy = NULL;
|
||||
}
|
||||
|
||||
vSemaphoreDelete(refresh_finish);
|
||||
refresh_finish = NULL;
|
||||
|
||||
#if TEST_PIN_NUM_BK_LIGHT >= 0
|
||||
TEST_ESP_OK(gpio_reset_pin(TEST_PIN_NUM_BK_LIGHT));
|
||||
TEST_ESP_OK(gpio_reset_pin(TEST_PIN_NUM_POWER));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void test_draw_color_bar(esp_lcd_panel_handle_t panel_handle, uint16_t h_res, uint16_t v_res)
|
||||
{
|
||||
uint8_t byte_per_pixel = (TEST_LCD_BIT_PER_PIXEL + 7) / 8;
|
||||
uint16_t row_line = v_res / byte_per_pixel / 8;
|
||||
uint8_t *color = (uint8_t *)heap_caps_calloc(1, row_line * h_res * byte_per_pixel, MALLOC_CAP_DMA);
|
||||
|
||||
for (int j = 0; j < byte_per_pixel * 8; j++) {
|
||||
for (int i = 0; i < row_line * h_res; i++) {
|
||||
for (int k = 0; k < byte_per_pixel; k++) {
|
||||
color[i * byte_per_pixel + k] = (BIT(j) >> (k * 8)) & 0xff;
|
||||
}
|
||||
}
|
||||
TEST_ESP_OK(esp_lcd_panel_draw_bitmap(panel_handle, 0, j * row_line, h_res, (j + 1) * row_line, color));
|
||||
xSemaphoreTake(refresh_finish, portMAX_DELAY);
|
||||
}
|
||||
|
||||
uint16_t color_line = row_line * byte_per_pixel * 8;
|
||||
uint16_t res_line = v_res - color_line;
|
||||
if (res_line) {
|
||||
for (int i = 0; i < res_line * h_res; i++) {
|
||||
for (int k = 0; k < byte_per_pixel; k++) {
|
||||
color[i * byte_per_pixel + k] = 0xff;
|
||||
}
|
||||
}
|
||||
TEST_ESP_OK(esp_lcd_panel_draw_bitmap(panel_handle, 0, color_line, h_res, v_res, color));
|
||||
xSemaphoreTake(refresh_finish, portMAX_DELAY);
|
||||
}
|
||||
|
||||
free(color);
|
||||
}
|
||||
|
||||
TEST_CASE("test st7796 to draw pattern with MIPI interface", "[st7796][draw_pattern]")
|
||||
{
|
||||
ESP_LOGI(TAG, "Initialize LCD device");
|
||||
test_init_lcd();
|
||||
|
||||
ESP_LOGI(TAG, "Show color bar pattern drawn by hardware");
|
||||
TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(panel_handle, MIPI_DSI_PATTERN_BAR_VERTICAL));
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_TIME_MS));
|
||||
TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(panel_handle, MIPI_DSI_PATTERN_BAR_HORIZONTAL));
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_TIME_MS));
|
||||
TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(panel_handle, MIPI_DSI_PATTERN_NONE));
|
||||
|
||||
ESP_LOGI(TAG, "Deinitialize LCD device");
|
||||
test_deinit_lcd();
|
||||
}
|
||||
|
||||
TEST_CASE("test st7796 to draw color bar with MIPI interface", "[st7796][draw_color_bar]")
|
||||
{
|
||||
ESP_LOGI(TAG, "Initialize LCD device");
|
||||
test_init_lcd();
|
||||
|
||||
ESP_LOGI(TAG, "Show color bar drawn by software");
|
||||
test_draw_color_bar(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_TIME_MS));
|
||||
|
||||
ESP_LOGI(TAG, "Deinitialize LCD device");
|
||||
test_deinit_lcd();
|
||||
}
|
||||
|
||||
TEST_CASE("test st7796 to rotate with MIPI interface", "[st7796][rotate]")
|
||||
{
|
||||
ESP_LOGI(TAG, "Initialize LCD device");
|
||||
test_init_lcd();
|
||||
|
||||
ESP_LOGI(TAG, "Mirror the screen");
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
TEST_ASSERT_NOT_EQUAL(esp_lcd_panel_mirror(panel_handle, i & 2, i & 1), ESP_FAIL);
|
||||
|
||||
ESP_LOGI(TAG, "Mirror: %d", i);
|
||||
test_draw_color_bar(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES);
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Deinitialize LCD device");
|
||||
test_deinit_lcd();
|
||||
}
|
||||
|
||||
#endif
|
||||
12
Libraries/esp_lcd_st7796/test_apps/sdkconfig.defaults
Normal file
12
Libraries/esp_lcd_st7796/test_apps/sdkconfig.defaults
Normal file
@ -0,0 +1,12 @@
|
||||
CONFIG_IDF_TARGET="esp32s3"
|
||||
CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_PERF=y
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MODE_OCT=y
|
||||
CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y
|
||||
CONFIG_SPIRAM_RODATA=y
|
||||
CONFIG_SPIRAM_SPEED_80M=y
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
|
||||
CONFIG_ESP_TASK_WDT_EN=n
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096
|
||||
@ -20,7 +20,11 @@ protected:
|
||||
|
||||
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 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 readRegister12(uint8_t reg, float& out) const;
|
||||
bool readRegister14(uint8_t reg, float& out) const;
|
||||
|
||||
@ -4,6 +4,22 @@
|
||||
|
||||
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 {
|
||||
std::uint8_t data[2] = {0};
|
||||
if (tt::hal::i2c::masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) {
|
||||
|
||||
@ -28,6 +28,34 @@ static lv_obj_t* createAppViews(Gui* gui, lv_obj_t* parent) {
|
||||
return child_container;
|
||||
}
|
||||
|
||||
lv_obj_tree_walk_res_t add_to_group(lv_obj_t * obj, void * user_data)
|
||||
{
|
||||
lv_group_t *group = (lv_group_t*)user_data;
|
||||
TT_LOG_I(TAG, "walk");
|
||||
|
||||
if (lv_obj_check_type(obj, &lv_button_class) ||
|
||||
lv_obj_check_type(obj, &lv_list_button_class) ||
|
||||
lv_obj_check_type(obj, &lv_textarea_class) ||
|
||||
lv_obj_check_type(obj, &lv_dropdown_class)) {
|
||||
lv_group_add_obj(group, obj);
|
||||
}
|
||||
return LV_OBJ_TREE_WALK_NEXT;
|
||||
}
|
||||
|
||||
static void add_child_to_default_group(lv_event_t * e)
|
||||
{
|
||||
lv_obj_t * child = (lv_obj_t *)lv_event_get_param(e);
|
||||
|
||||
TT_LOG_I(TAG, "added %X", (unsigned)lv_obj_get_class(child));
|
||||
if (lv_obj_check_type(child, &lv_list_text_class)) {
|
||||
lv_obj_add_flag(child, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_flag(child, LV_OBJ_FLAG_CLICK_FOCUSABLE);
|
||||
lv_obj_add_flag(child, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
|
||||
lv_group_add_obj(lv_group_get_default(), child);
|
||||
TT_LOG_I(TAG, "listitem set");
|
||||
}
|
||||
}
|
||||
|
||||
void redraw(Gui* gui) {
|
||||
assert(gui);
|
||||
|
||||
@ -39,6 +67,20 @@ void redraw(Gui* gui) {
|
||||
|
||||
if (gui->appToRender != nullptr) {
|
||||
|
||||
|
||||
lv_group_t *group = lv_group_create();
|
||||
auto* indev = lv_indev_get_next(nullptr);
|
||||
while(indev) {
|
||||
TT_LOG_I(TAG, "Added indev %X", (unsigned)indev);
|
||||
lv_indev_set_group(indev, group);
|
||||
indev = lv_indev_get_next(indev);
|
||||
}
|
||||
lv_group_set_default(group);
|
||||
|
||||
//lv_obj_add_flag(gui->appRootWidget, LV_OBJ_FLAG_SCROLL_CHAIN_VER);
|
||||
//lv_gridnav_add(gui->appRootWidget, (lv_gridnav_ctrl_t)(LV_GRIDNAV_CTRL_ROLLOVER | LV_GRIDNAV_CTRL_SCROLL_FIRST | LV_GRIDNAV_CTRL_VERTICAL_MOVE_ONLY));
|
||||
//lv_group_add_obj(group, gui->appRootWidget);
|
||||
|
||||
app::Flags flags = std::static_pointer_cast<app::AppInstance>(gui->appToRender)->getFlags();
|
||||
if (flags.showStatusbar) {
|
||||
lv_obj_remove_flag(gui->statusbarWidget, LV_OBJ_FLAG_HIDDEN);
|
||||
@ -48,6 +90,11 @@ void redraw(Gui* gui) {
|
||||
|
||||
lv_obj_t* container = createAppViews(gui, gui->appRootWidget);
|
||||
gui->appToRender->getApp()->onShow(*gui->appToRender, container);
|
||||
|
||||
//lv_obj_tree_walk(container, add_to_group, group);
|
||||
|
||||
//lv_obj_add_event_cb(gui->appRootWidget, add_child_to_default_group, LV_EVENT_CHILD_CREATED, NULL);
|
||||
|
||||
} else {
|
||||
TT_LOG_W(TAG, "nothing to draw");
|
||||
}
|
||||
|
||||
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