Merge develop into main (#368)

New boards:
- LilyGO T-Dongle S3
- M5Stack StickC Plus
- M5Stack StickC Plus2

New drivers:
- AXP192: power control via I2C
- ButtonControl: GPIO button input as LVGL device

Other changes:
- Updated implementation of AXP192 driver for Core2 board
- Fix launcher UX for vertical layout
- Fix error when properties file had an empty line
- Add `__floatsidf` to `tt_init.cpp`
This commit is contained in:
Ken Van Hoeylandt
2025-10-14 20:39:23 +02:00
committed by GitHub
parent 3a59540365
commit d8346998ce
68 changed files with 1857 additions and 142 deletions
+8
View File
@@ -0,0 +1,8 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Include"
PRIV_INCLUDE_DIRS "Private"
REQUIRES Tactility
)
+59
View File
@@ -0,0 +1,59 @@
#pragma once
#include <axp192/axp192.h>
#include <Tactility/hal/power/PowerDevice.h>
#include <Tactility/hal/i2c/I2c.h>
#include <memory>
class Axp192 final : public tt::hal::power::PowerDevice {
static int32_t i2cRead(void* handle, uint8_t address, uint8_t reg, uint8_t* buffer, uint16_t size);
static int32_t i2cWrite(void* handle, uint8_t address, uint8_t reg, const uint8_t* buffer, uint16_t size);
public:
struct Configuration {
i2c_port_t port;
TickType_t readTimeout = 50 / portTICK_PERIOD_MS;
TickType_t writeTimeout = 50 / portTICK_PERIOD_MS;
};
private:
std::unique_ptr<Configuration> configuration;
axp192_t axpDevice = {
.read = i2cRead,
.write = i2cWrite,
.handle = this
};
bool isInitialized = false;
public:
explicit Axp192(std::unique_ptr<Configuration> configuration) : configuration(std::move(configuration)) {}
~Axp192() override {}
/**
* @warning Must call this function before device can operate!
* @param onInit
*/
bool init(const std::function<bool(axp192_t*)>& onInit) {
isInitialized = onInit(&axpDevice);
return isInitialized;
}
axp192_t* getAxp192() { return &axpDevice; }
std::string getName() const override { return "AXP192"; }
std::string getDescription() const override { return "AXP192 power management via I2C"; }
bool supportsMetric(MetricType type) const override;
bool getMetric(MetricType type, MetricData& data) override;
bool supportsChargeControl() const override { return true; }
bool isAllowedToCharge() const override;
void setAllowedToCharge(bool canCharge) override;
};
+213
View File
@@ -0,0 +1,213 @@
/*
MIT License
Copyright (c) 2019-2021 Mika Tuupola
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-cut-
This file is part of hardware agnostic I2C driver for AXP192:
https://github.com/tuupola/axp192
SPDX-License-Identifier: MIT
Version: 0.6.0
*/
#ifndef _AXP192_H
#define _AXP192_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#define AXP192_ADDRESS (0x34)
/* Power control registers */
#define AXP192_POWER_STATUS (0x00)
#define AXP192_CHARGE_STATUS (0x01)
#define AXP192_OTG_VBUS_STATUS (0x04)
#define AXP192_DATA_BUFFER0 (0x06)
#define AXP192_DATA_BUFFER1 (0x07)
#define AXP192_DATA_BUFFER2 (0x08)
#define AXP192_DATA_BUFFER3 (0x09)
#define AXP192_DATA_BUFFER4 (0x0a)
#define AXP192_DATA_BUFFER5 (0x0b)
/* Output control: 2 EXTEN, 0 DCDC2 */
#define AXP192_EXTEN_DCDC2_CONTROL (0x10)
/* Power output control: 6 EXTEN, 4 DCDC2, 3 LDO3, 2 LDO2, 1 DCDC3, 0 DCDC1 */
#define AXP192_DCDC13_LDO23_CONTROL (0x12)
#define AXP192_DCDC2_VOLTAGE (0x23)
#define AXP192_DCDC2_SLOPE (0x25)
#define AXP192_DCDC1_VOLTAGE (0x26)
#define AXP192_DCDC3_VOLTAGE (0x27)
/* Output voltage control: 7-4 LDO2, 3-0 LDO3 */
#define AXP192_LDO23_VOLTAGE (0x28)
#define AXP192_VBUS_IPSOUT_CHANNEL (0x30)
#define AXP192_SHUTDOWN_VOLTAGE (0x31)
#define AXP192_SHUTDOWN_BATTERY_CHGLED_CONTROL (0x32)
#define AXP192_CHARGE_CONTROL_1 (0x33)
#define AXP192_CHARGE_CONTROL_2 (0x34)
#define AXP192_BATTERY_CHARGE_CONTROL (0x35)
#define AXP192_PEK (0x36)
#define AXP192_DCDC_FREQUENCY (0x37)
#define AXP192_BATTERY_CHARGE_LOW_TEMP (0x38)
#define AXP192_BATTERY_CHARGE_HIGH_TEMP (0x39)
#define AXP192_APS_LOW_POWER1 (0x3A)
#define AXP192_APS_LOW_POWER2 (0x3B)
#define AXP192_BATTERY_DISCHARGE_LOW_TEMP (0x3c)
#define AXP192_BATTERY_DISCHARGE_HIGH_TEMP (0x3d)
#define AXP192_DCDC_MODE (0x80)
#define AXP192_ADC_ENABLE_1 (0x82)
#define AXP192_ADC_ENABLE_2 (0x83)
#define AXP192_ADC_RATE_TS_PIN (0x84)
#define AXP192_GPIO30_INPUT_RANGE (0x85)
#define AXP192_GPIO0_ADC_IRQ_RISING (0x86)
#define AXP192_GPIO0_ADC_IRQ_FALLING (0x87)
#define AXP192_TIMER_CONTROL (0x8a)
#define AXP192_VBUS_MONITOR (0x8b)
#define AXP192_TEMP_SHUTDOWN_CONTROL (0x8f)
/* GPIO control registers */
#define AXP192_GPIO0_CONTROL (0x90)
#define AXP192_GPIO0_LDOIO0_VOLTAGE (0x91)
#define AXP192_GPIO1_CONTROL (0x92)
#define AXP192_GPIO2_CONTROL (0x93)
#define AXP192_GPIO20_SIGNAL_STATUS (0x94)
#define AXP192_GPIO43_FUNCTION_CONTROL (0x95)
#define AXP192_GPIO43_SIGNAL_STATUS (0x96)
#define AXP192_GPIO20_PULLDOWN_CONTROL (0x97)
#define AXP192_PWM1_FREQUENCY (0x98)
#define AXP192_PWM1_DUTY_CYCLE_1 (0x99)
#define AXP192_PWM1_DUTY_CYCLE_2 (0x9a)
#define AXP192_PWM2_FREQUENCY (0x9b)
#define AXP192_PWM2_DUTY_CYCLE_1 (0x9c)
#define AXP192_PWM2_DUTY_CYCLE_2 (0x9d)
#define AXP192_N_RSTO_GPIO5_CONTROL (0x9e)
/* Interrupt control registers */
#define AXP192_ENABLE_CONTROL_1 (0x40)
#define AXP192_ENABLE_CONTROL_2 (0x41)
#define AXP192_ENABLE_CONTROL_3 (0x42)
#define AXP192_ENABLE_CONTROL_4 (0x43)
#define AXP192_ENABLE_CONTROL_5 (0x4a)
#define AXP192_IRQ_STATUS_1 (0x44)
#define AXP192_IRQ_STATUS_2 (0x45)
#define AXP192_IRQ_STATUS_3 (0x46)
#define AXP192_IRQ_STATUS_4 (0x47)
#define AXP192_IRQ_STATUS_5 (0x4d)
/* ADC data registers */
#define AXP192_ACIN_VOLTAGE (0x56)
#define AXP192_ACIN_CURRENT (0x58)
#define AXP192_VBUS_VOLTAGE (0x5a)
#define AXP192_VBUS_CURRENT (0x5c)
#define AXP192_TEMP (0x5e)
#define AXP192_TS_INPUT (0x62)
#define AXP192_GPIO0_VOLTAGE (0x64)
#define AXP192_GPIO1_VOLTAGE (0x66)
#define AXP192_GPIO2_VOLTAGE (0x68)
#define AXP192_GPIO3_VOLTAGE (0x6a)
#define AXP192_BATTERY_POWER (0x70)
#define AXP192_BATTERY_VOLTAGE (0x78)
#define AXP192_CHARGE_CURRENT (0x7a)
#define AXP192_DISCHARGE_CURRENT (0x7c)
#define AXP192_APS_VOLTAGE (0x7e)
#define AXP192_CHARGE_COULOMB (0xb0)
#define AXP192_DISCHARGE_COULOMB (0xb4)
#define AXP192_COULOMB_COUNTER_CONTROL (0xb8)
/* Computed ADC */
#define AXP192_COULOMB_COUNTER (0xff)
/* IOCTL commands */
#define AXP192_READ_POWER_STATUS (0x0001)
#define AXP192_READ_CHARGE_STATUS (0x0101)
#define AXP192_COULOMB_COUNTER_ENABLE (0xb801)
#define AXP192_COULOMB_COUNTER_DISABLE (0xb802)
#define AXP192_COULOMB_COUNTER_SUSPEND (0xb803)
#define AXP192_COULOMB_COUNTER_CLEAR (0xb804)
#define AXP192_LDOIO0_ENABLE (0x9000)
#define AXP192_LDOIO0_DISABLE (0x9001)
#define AXP192_DCDC2_ENABLE (0x1000)
#define AXP192_DCDC2_DISABLE (0x1001)
#define AXP192_EXTEN_ENABLE (0x1002)
#define AXP192_EXTEN_DISABLE (0x1003)
#define AXP192_LDO2_ENABLE (0x1200)
#define AXP192_LDO2_DISABLE (0x1201)
#define AXP192_LDO3_ENABLE (0x1202)
#define AXP192_LDO3_DISABLE (0x1203)
#define AXP192_DCDC1_ENABLE (0x1204)
#define AXP192_DCDC1_DISABLE (0x1205)
#define AXP192_DCDC3_ENABLE (0x1206)
#define AXP192_DCDC3_DISABLE (0x1207)
#define AXP192_DCDC1_SET_VOLTAGE (0x2600)
#define AXP192_DCDC2_SET_VOLTAGE (0x2300)
#define AXP192_DCDC3_SET_VOLTAGE (0x2700)
#define AXP192_LDO2_SET_VOLTAGE (0x2800)
#define AXP192_LDO3_SET_VOLTAGE (0x2801)
#define AXP192_LDOIO0_SET_VOLTAGE (0x9100)
#define AXP192_LOW (0)
#define AXP192_HIGH (1)
#define AXP192_GPIO0_SET_LEVEL (0x9400)
#define AXP192_GPIO1_SET_LEVEL (0x9401)
#define AXP192_GPIO2_SET_LEVEL (0x9402)
#define AXP192_GPIO4_SET_LEVEL (0x9601)
/* Error codes */
#define AXP192_OK (0)
#define AXP192_ERROR_NOTTY (-1)
#define AXP192_ERROR_EINVAL (-22)
#define AXP192_ERROR_ENOTSUP (-95)
typedef struct {
uint8_t command;
uint8_t data[2];
uint8_t count;
} axp192_init_command_t;
/* These should be provided by the HAL. */
typedef struct {
int32_t (* read)(void *handle, uint8_t address, uint8_t reg, uint8_t *buffer, uint16_t size);
int32_t (* write)(void *handle, uint8_t address, uint8_t reg, const uint8_t *buffer, uint16_t size);
void *handle;
} axp192_t;
typedef int32_t axp192_err_t;
axp192_err_t axp192_init(const axp192_t *axp);
axp192_err_t axp192_read(const axp192_t *axp, uint8_t reg, void *buffer);
axp192_err_t axp192_write(const axp192_t *axp, uint8_t reg, uint8_t value);
axp192_err_t axp192_ioctl(const axp192_t *axp, int command, ...);
#ifdef __cplusplus
}
#endif
#endif
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019-2021 Mika Tuupola
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+4
View File
@@ -0,0 +1,4 @@
From https://github.com/tuupola/axp192
Changed some code for Tactility:
- `axp192_write()` now accepts a byte value instead of a byte pointer
@@ -0,0 +1,125 @@
/*
MIT License
Copyright (c) 2019-2021 Mika Tuupola
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-cut-
This file is part of hardware agnostic I2C driver for AXP192:
https://github.com/tuupola/axp192
SPDX-License-Identifier: MIT
Version: 0.6.0
*/
#ifndef _AXP192_CONFIG_H
#define _AXP192_CONFIG_H
#ifdef __cplusplus
extern "C" {
#endif
#ifdef AXP192_INCLUDE_SDKCONFIG_H
#include "sdkconfig.h"
/* This requires you to run menuconfig first. */
#define CONFIG_AXP192_EXTEN_DCDC2_CONTROL ( \
CONFIG_AXP192_EXTEN_DCDC2_CONTROL_BIT2 | \
CONFIG_AXP192_EXTEN_DCDC2_CONTROL_BIT0 \
)
#define CONFIG_AXP192_DCDC13_LDO23_CONTROL ( \
CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT6 | \
CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT4 | \
CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT3 | \
CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT2 | \
CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT1 | \
CONFIG_AXP192_DCDC13_LDO23_CONTROL_BIT0 \
)
#define CONFIG_AXP192_LDO23_VOLTAGE ( \
CONFIG_AXP192_LDO23_VOLTAGE_BIT74 | \
CONFIG_AXP192_LDO23_VOLTAGE_BIT30 \
)
#define CONFIG_AXP192_DCDC1_VOLTAGE ( \
CONFIG_AXP192_DCDC1_VOLTAGE_BIT60 \
)
#define CONFIG_AXP192_DCDC3_VOLTAGE ( \
CONFIG_AXP192_DCDC3_VOLTAGE_BIT60 \
)
#define CONFIG_AXP192_ADC_ENABLE_1 ( \
CONFIG_AXP192_ADC_ENABLE_1_BIT7 | \
CONFIG_AXP192_ADC_ENABLE_1_BIT6 | \
CONFIG_AXP192_ADC_ENABLE_1_BIT5 | \
CONFIG_AXP192_ADC_ENABLE_1_BIT4 | \
CONFIG_AXP192_ADC_ENABLE_1_BIT3 | \
CONFIG_AXP192_ADC_ENABLE_1_BIT2 | \
CONFIG_AXP192_ADC_ENABLE_1_BIT1 | \
CONFIG_AXP192_ADC_ENABLE_1_BIT0 \
)
#define CONFIG_AXP192_CHARGE_CONTROL_1 ( \
CONFIG_AXP192_CHARGE_CONTROL_1_BIT7 | \
CONFIG_AXP192_CHARGE_CONTROL_1_BIT65 | \
CONFIG_AXP192_CHARGE_CONTROL_1_BIT4 | \
CONFIG_AXP192_CHARGE_CONTROL_1_BIT30 \
)
#define CONFIG_AXP192_BATTERY_CHARGE_CONTROL ( \
CONFIG_AXP192_BATTERY_CHARGE_CONTROL_BIT7 | \
CONFIG_AXP192_BATTERY_CHARGE_CONTROL_BIT65 | \
CONFIG_AXP192_BATTERY_CHARGE_CONTROL_BIT10 \
)
#define CONFIG_AXP192_GPIO0_CONTROL ( \
CONFIG_AXP192_GPIO0_CONTROL_BIT20 \
)
#define CONFIG_AXP192_GPIO1_CONTROL ( \
CONFIG_AXP192_GPIO1_CONTROL_BIT20 \
)
#define CONFIG_AXP192_GPIO2_CONTROL ( \
CONFIG_AXP192_GPIO2_CONTROL_BIT20 \
)
#define CONFIG_AXP192_GPIO43_FUNCTION_CONTROL ( \
CONFIG_AXP192_GPIO43_FUNCTION_CONTROL_BIT7 | \
CONFIG_AXP192_GPIO43_FUNCTION_CONTROL_BIT32 | \
CONFIG_AXP192_GPIO43_FUNCTION_CONTROL_BIT10 \
)
#define CONFIG_AXP192_GPIO0_LDOIO0_VOLTAGE ( \
CONFIG_AXP192_GPIO0_LDOIO0_VOLTAGE_BIT74 \
)
#endif /* AXP192_INCLUDE_SDKCONFIG_H */
#ifdef __cplusplus
}
#endif
#endif
+5
View File
@@ -0,0 +1,5 @@
# AXP192
Power management with I2C interface. Used on M5Stack Core2 and StickCPlus.
It includes driver code from https://github.com/tuupola/axp192, Copyright (c) 2019-2021 Mika Tuupola, MIT License
+117
View File
@@ -0,0 +1,117 @@
#include "Axp192.h"
constexpr auto TAG = "Axp192Power";
int32_t Axp192::i2cRead(void* handle, uint8_t address, uint8_t reg, uint8_t* buffer, uint16_t size) {
const auto* device = static_cast<Axp192*>(handle);
if (tt::hal::i2c::masterReadRegister(device->configuration->port, address, reg, buffer, size, device->configuration->readTimeout)) {
return AXP192_OK;
} else {
return 1;
}
}
int32_t Axp192::i2cWrite(void* handle, uint8_t address, uint8_t reg, const uint8_t* buffer, uint16_t size) {
const auto* device = static_cast<Axp192*>(handle);
if (tt::hal::i2c::masterWriteRegister(device->configuration->port, address, reg, buffer, size, device->configuration->writeTimeout)) {
return AXP192_OK;
} else {
return 1;
}
}
bool Axp192::supportsMetric(MetricType type) const {
if (!isInitialized) {
return false;
}
switch (type) {
using enum MetricType;
case BatteryVoltage:
case ChargeLevel:
case IsCharging:
return true;
default:
return false;
}
}
bool Axp192::getMetric(MetricType type, MetricData& data) {
switch (type) {
using enum MetricType;
case BatteryVoltage: {
float voltage;
if (axp192_read(&axpDevice, AXP192_BATTERY_VOLTAGE, &voltage) == ESP_OK) {
data.valueAsUint32 = (uint32_t)std::max((voltage * 1000.f), 0.0f);
return true;
} else {
return false;
}
}
case ChargeLevel: {
float vbat, charge_current;
if (
axp192_read(&axpDevice, AXP192_BATTERY_VOLTAGE, &vbat) == ESP_OK &&
axp192_read(&axpDevice, AXP192_CHARGE_CURRENT, &charge_current) == ESP_OK
) {
float max_voltage = 4.20f;
float min_voltage = 2.69f; // From M5Unified
float voltage_correction = (charge_current > 0.01f) ? -0.1f : 0.f; // Roughly 0.1V drop when ccharging
float corrected_voltage = vbat + voltage_correction;
if (corrected_voltage > 2.69f) {
float charge_factor = (corrected_voltage - min_voltage) / (max_voltage - min_voltage);
data.valueAsUint8 = (uint8_t)(charge_factor * 100.f);
} else {
data.valueAsUint8 = 0;
}
return true;
} else {
return false;
}
}
case IsCharging: {
float charge_current;
if (axp192_read(&axpDevice, AXP192_CHARGE_CURRENT, &charge_current) == ESP_OK) {
data.valueAsBool = charge_current > 0.001f;
return true;
} else {
return false;
}
}
case Current: {
float charge_current, discharge_current;
if (
axp192_read(&axpDevice, AXP192_CHARGE_CURRENT, &charge_current) == ESP_OK &&
axp192_read(&axpDevice, AXP192_DISCHARGE_CURRENT, &discharge_current) == ESP_OK
) {
if (charge_current > 0.0f) {
data.valueAsInt32 = (int32_t) (charge_current * 1000.0f);
} else {
data.valueAsInt32 = -(int32_t) (discharge_current * 1000.0f);
}
return true;
} else {
return false;
}
}
default:
return false;
}
}
bool Axp192::isAllowedToCharge() const {
uint8_t buffer;
if (axp192_read(&axpDevice, AXP192_CHARGE_CONTROL_1, &buffer) == ESP_OK) {
return buffer & 0x80;
} else {
return false;
}
}
void Axp192::setAllowedToCharge(bool canCharge) {
uint8_t buffer;
if (axp192_read(&axpDevice, AXP192_CHARGE_CONTROL_1, &buffer) == ESP_OK) {
buffer = (buffer & 0x7F) + (canCharge ? 0x80 : 0x00);
axp192_write(&axpDevice, AXP192_CHARGE_CONTROL_1, buffer);
}
}
+440
View File
@@ -0,0 +1,440 @@
/*
MIT License
Copyright (c) 2019-2021 Mika Tuupola
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-cut-
This file is part of hardware agnostic I2C driver for AXP192:
https://github.com/tuupola/axp192
SPDX-License-Identifier: MIT
Version: 0.6.0
*/
#include <stdarg.h>
#include <stdint.h>
#include "axp192/axp192_config.h"
#include "axp192/axp192.h"
static axp192_err_t read_coloumb_counter(const axp192_t *axp, float *buffer);
static axp192_err_t read_battery_power(const axp192_t *axp, float *buffer);
static const axp192_init_command_t init_commands[] = {
#ifdef AXP192_INCLUDE_SDKCONFIG_H
/* Currently you have to use menuconfig to be able to use axp192_init() */
{AXP192_DCDC1_VOLTAGE, {CONFIG_AXP192_DCDC1_VOLTAGE}, 1},
{AXP192_DCDC3_VOLTAGE, {CONFIG_AXP192_DCDC3_VOLTAGE}, 1},
{AXP192_LDO23_VOLTAGE, {CONFIG_AXP192_LDO23_VOLTAGE}, 1},
{AXP192_GPIO0_LDOIO0_VOLTAGE, {CONFIG_AXP192_GPIO0_LDOIO0_VOLTAGE}, 1},
{AXP192_DCDC13_LDO23_CONTROL, {CONFIG_AXP192_DCDC13_LDO23_CONTROL}, 1},
{AXP192_EXTEN_DCDC2_CONTROL, {CONFIG_AXP192_EXTEN_DCDC2_CONTROL}, 1},
{AXP192_GPIO0_CONTROL, {CONFIG_AXP192_GPIO0_CONTROL}, 1},
{AXP192_GPIO1_CONTROL, {CONFIG_AXP192_GPIO1_CONTROL}, 1},
{AXP192_GPIO2_CONTROL, {CONFIG_AXP192_GPIO2_CONTROL}, 1},
{AXP192_GPIO43_FUNCTION_CONTROL, {CONFIG_AXP192_GPIO43_FUNCTION_CONTROL}, 1},
{AXP192_ADC_ENABLE_1, {CONFIG_AXP192_ADC_ENABLE_1}, 1},
{AXP192_CHARGE_CONTROL_1, {CONFIG_AXP192_CHARGE_CONTROL_1}, 1},
{AXP192_BATTERY_CHARGE_CONTROL, {CONFIG_AXP192_BATTERY_CHARGE_CONTROL}, 1},
#endif /* AXP192_INCLUDE_SDKCONFIG_H */
/* End of commands. */
{0, {0}, 0xff},
};
axp192_err_t axp192_init(const axp192_t *axp)
{
uint8_t cmd = 0;
axp192_err_t status;
/* Send all the commands. */
while (init_commands[cmd].count != 0xff) {
status = axp->write(
axp->handle,
AXP192_ADDRESS,
init_commands[cmd].command,
init_commands[cmd].data,
init_commands[cmd].count & 0x1f
);
if (AXP192_OK != status) {
return status;
}
cmd++;
}
return AXP192_OK;
}
static axp192_err_t axp192_read_adc(const axp192_t *axp, uint8_t reg, float *buffer)
{
uint8_t tmp[4];
float sensitivity = 1.0;
float offset = 0.0;
axp192_err_t status;
switch (reg) {
case AXP192_ACIN_VOLTAGE:
case AXP192_VBUS_VOLTAGE:
/* 1.7mV per LSB */
sensitivity = 1.7 / 1000;
break;
case AXP192_ACIN_CURRENT:
/* 0.375mA per LSB */
sensitivity = 0.625 / 1000;
break;
case AXP192_VBUS_CURRENT:
/* 0.375mA per LSB */
sensitivity = 0.375 / 1000;
break;
case AXP192_TEMP:
/* 0.1C per LSB, 0x00 = -144.7C */
sensitivity = 0.1;
offset = -144.7;
break;
case AXP192_TS_INPUT:
/* 0.8mV per LSB */
sensitivity = 0.8 / 1000;
break;
case AXP192_BATTERY_POWER:
/* 1.1mV * 0.5mA per LSB */
return read_battery_power(axp, buffer);
break;
case AXP192_BATTERY_VOLTAGE:
/* 1.1mV per LSB */
sensitivity = 1.1 / 1000;
break;
case AXP192_CHARGE_CURRENT:
case AXP192_DISCHARGE_CURRENT:
/* 0.5mV per LSB */
sensitivity = 0.5 / 1000;
break;
case AXP192_APS_VOLTAGE:
/* 1.4mV per LSB */
sensitivity = 1.4 / 1000;
break;
case AXP192_COULOMB_COUNTER:
/* This is currently untested. */
return read_coloumb_counter(axp, buffer);
break;
}
status = axp->read(axp->handle, AXP192_ADDRESS, reg, tmp, 2);
if (AXP192_OK != status) {
return status;
}
*buffer = (((tmp[0] << 4) + tmp[1]) * sensitivity) + offset;
return AXP192_OK;
}
axp192_err_t axp192_read(const axp192_t *axp, uint8_t reg, void *buffer) {
switch (reg) {
case AXP192_ACIN_VOLTAGE:
case AXP192_VBUS_VOLTAGE:
case AXP192_ACIN_CURRENT:
case AXP192_VBUS_CURRENT:
case AXP192_TEMP:
case AXP192_TS_INPUT:
case AXP192_BATTERY_POWER:
case AXP192_BATTERY_VOLTAGE:
case AXP192_CHARGE_CURRENT:
case AXP192_DISCHARGE_CURRENT:
case AXP192_APS_VOLTAGE:
case AXP192_COULOMB_COUNTER:
/* Return ADC value. */
return axp192_read_adc(axp, reg, buffer);
break;
default:
/* Return raw register value. */
return axp->read(axp->handle, AXP192_ADDRESS, reg, buffer, 1);
}
}
axp192_err_t axp192_write(const axp192_t *axp, uint8_t reg, uint8_t value) {
switch (reg) {
case AXP192_ACIN_VOLTAGE:
case AXP192_VBUS_VOLTAGE:
case AXP192_ACIN_CURRENT:
case AXP192_VBUS_CURRENT:
case AXP192_TEMP:
case AXP192_TS_INPUT:
case AXP192_BATTERY_POWER:
case AXP192_BATTERY_VOLTAGE:
case AXP192_CHARGE_CURRENT:
case AXP192_DISCHARGE_CURRENT:
case AXP192_APS_VOLTAGE:
case AXP192_COULOMB_COUNTER:
/* Read only register. */
return AXP192_ERROR_ENOTSUP;
break;
default:
/* Write raw register value. */
return axp->write(axp->handle, AXP192_ADDRESS, reg, &value, 1);
}
}
axp192_err_t axp192_ioctl(const axp192_t *axp, int command, ...)
{
uint8_t reg = command >> 8;
uint8_t tmp;
uint16_t argument;
va_list ap;
switch (command) {
case AXP192_COULOMB_COUNTER_ENABLE:
tmp = 0b10000000;
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
case AXP192_COULOMB_COUNTER_DISABLE:
tmp = 0b00000000;
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
case AXP192_COULOMB_COUNTER_SUSPEND:
tmp = 0b11000000;
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
case AXP192_COULOMB_COUNTER_CLEAR:
tmp = 0b10100000;
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
/* This is currently untested. */
case AXP192_LDOIO0_ENABLE:
/* 0x02 = LDO */
tmp = 0b00000010;
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
/* This is currently untested. */
case AXP192_LDOIO0_DISABLE:
/* 0x07 = float */
tmp = 0b00000111;
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
case AXP192_LDO2_ENABLE:
/* This is currently untested. */
case AXP192_EXTEN_ENABLE:
axp->read(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
tmp |= 0b00000100;
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
case AXP192_LDO2_DISABLE:
/* This is currently untested. */
case AXP192_EXTEN_DISABLE:
axp->read(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
tmp &= ~0b00000100;
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
/* This is currently untested. */
case AXP192_GPIO2_SET_LEVEL:
axp->read(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
va_start(ap, command);
argument = (uint8_t) va_arg(ap, int);
va_end(ap);
if (argument) {
tmp |= 0b00000100;
} else {
tmp &= ~0b00000100;
}
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
case AXP192_LDO3_ENABLE:
axp->read(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
tmp |= 0b00001000;
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
case AXP192_LDO3_DISABLE:
axp->read(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
tmp &= ~0b00001000;
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
/* This is currently untested. */
case AXP192_DCDC1_ENABLE:
/* This is currently untested. */
case AXP192_DCDC2_ENABLE:
axp->read(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
tmp |= 0b00000001;
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
/* This is currently untested. */
case AXP192_DCDC1_DISABLE:
/* This is currently untested. */
case AXP192_DCDC2_DISABLE:
axp->read(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
tmp &= ~0b00000001;
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
case AXP192_DCDC3_ENABLE:
axp->read(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
tmp |= 0b00000010;
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
case AXP192_DCDC3_DISABLE:
axp->read(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
tmp &= ~0b00000010;
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
case AXP192_GPIO1_SET_LEVEL:
case AXP192_GPIO4_SET_LEVEL:
axp->read(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
va_start(ap, command);
argument = (uint8_t) va_arg(ap, int);
va_end(ap);
if (argument) {
tmp |= 0b00000010;
} else {
tmp &= ~0b00000010;
}
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
/* This is currently untested. */
case AXP192_GPIO0_SET_LEVEL:
axp->read(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
va_start(ap, command);
argument = (uint8_t) va_arg(ap, int);
va_end(ap);
if (argument) {
tmp |= 0b00000001;
} else {
tmp &= ~0b00000001;
}
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
case AXP192_DCDC1_SET_VOLTAGE:
case AXP192_DCDC3_SET_VOLTAGE:
va_start(ap, command);
argument = (uint16_t) va_arg(ap, int);
va_end(ap);
/* 700-3500mv 25mV per step */
if ((argument < 700) || (argument > 3500)) {
return AXP192_ERROR_EINVAL;
}
tmp = (argument - 700) / 25;
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
/* This is currently untested. */
case AXP192_DCDC2_SET_VOLTAGE:
va_start(ap, command);
argument = (uint16_t) va_arg(ap, int);
va_end(ap);
/* 700-2275mV 25mV per step */
if ((argument < 700) || (argument > 2275)) {
return AXP192_ERROR_EINVAL;
}
tmp = (argument - 700) / 25;
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
/* This is currently untested. */
case AXP192_LDO2_SET_VOLTAGE:
va_start(ap, command);
argument = (uint16_t) va_arg(ap, int);
va_end(ap);
/* 1800-3300mV 100mV per step */
if ((argument < 1800) || (argument > 3300)) {
return AXP192_ERROR_EINVAL;
}
axp->read(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
tmp &= ~0xf0;
tmp |= (((argument - 1800) / 100) << 4);
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
/* This is currently untested. */
case AXP192_LDO3_SET_VOLTAGE:
va_start(ap, command);
argument = (uint16_t) va_arg(ap, int);
va_end(ap);
/* 1800-3300mV 100mV per step */
if ((argument < 1800) || (argument > 3300)) {
return AXP192_ERROR_EINVAL;
}
axp->read(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
tmp &= ~0x0f;
tmp |= ((argument - 1800) / 100);
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
/* This is currently untested. */
case AXP192_LDOIO0_SET_VOLTAGE:
va_start(ap, command);
argument = (uint16_t) va_arg(ap, int);
va_end(ap);
/* 1800-3300mV 100mV per step, 2800mV default. */
if ((argument < 1800) || (argument > 3300)) {
return AXP192_ERROR_EINVAL;
}
tmp = (((argument - 1800) / 100) << 4);
return axp->write(axp->handle, AXP192_ADDRESS, reg, &tmp, 1);
break;
}
return AXP192_ERROR_NOTTY;
}
static axp192_err_t read_coloumb_counter(const axp192_t *axp, float *buffer)
{
uint8_t tmp[4];
int32_t coin, coout;
axp192_err_t status;
status = axp->read(axp->handle, AXP192_ADDRESS, AXP192_CHARGE_COULOMB, tmp, sizeof(coin));
if (AXP192_OK != status) {
return status;
}
coin = (tmp[0] << 24) + (tmp[1] << 16) + (tmp[2] << 8) + tmp[3];
status = axp->read(axp->handle, AXP192_ADDRESS, AXP192_DISCHARGE_COULOMB, tmp, sizeof(coout));
if (AXP192_OK != status) {
return status;
}
coout = (tmp[0] << 24) + (tmp[1] << 16) + (tmp[2] << 8) + tmp[3];
/* CmAh = 65536 * 0.5mA *coin - cout) / 3600 / ADC sample rate */
*buffer = 32768 * (coin - coout) / 3600 / 25;
return AXP192_OK;
}
static axp192_err_t read_battery_power(const axp192_t *axp, float *buffer)
{
uint8_t tmp[4];
float sensitivity;
axp192_err_t status;
/* 1.1mV * 0.5mA per LSB */
sensitivity = 1.1 * 0.5 / 1000;
status = axp->read(axp->handle, AXP192_ADDRESS, AXP192_BATTERY_POWER, tmp, 3);
if (AXP192_OK != status) {
return status;
}
*buffer = (((tmp[0] << 16) + (tmp[1] << 8) + tmp[2]) * sensitivity);
return AXP192_OK;
}
+7
View 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 driver
)
+3
View File
@@ -0,0 +1,3 @@
# Button Control
A driver for navigating apps with 1 or more GPIO buttons.
@@ -0,0 +1,162 @@
#include "ButtonControl.h"
#include <Tactility/Log.h>
#include <esp_lvgl_port.h>
constexpr auto* TAG = "ButtonControl";
ButtonControl::ButtonControl(const std::vector<PinConfiguration>& pinConfigurations) : pinConfigurations(pinConfigurations) {
pinStates.resize(pinConfigurations.size());
for (const auto& pinConfiguration : pinConfigurations) {
tt::hal::gpio::configure(pinConfiguration.pin, tt::hal::gpio::Mode::Input, false, false);
}
}
ButtonControl::~ButtonControl() {
if (driverThread != nullptr && driverThread->getState() != tt::Thread::State::Stopped) {
interruptDriverThread = true;
driverThread->join();
}
}
void ButtonControl::readCallback(lv_indev_t* indev, lv_indev_data_t* data) {
// Defaults
data->enc_diff = 0;
data->state = LV_INDEV_STATE_RELEASED;
auto* self = static_cast<ButtonControl*>(lv_indev_get_driver_data(indev));
if (self->mutex.lock(100)) {
for (int i = 0; i < self->pinConfigurations.size(); i++) {
const auto& config = self->pinConfigurations[i];
std::vector<PinState>::reference state = self->pinStates[i];
const bool trigger = (config.event == Event::ShortPress && state.triggerShortPress) ||
(config.event == Event::LongPress && state.triggerLongPress);
state.triggerShortPress = false;
state.triggerLongPress = false;
if (trigger) {
switch (config.action) {
case Action::UiSelectNext:
data->enc_diff = 1;
break;
case Action::UiSelectPrevious:
data->enc_diff = -1;
break;
case Action::UiPressSelected:
data->state = LV_INDEV_STATE_PRESSED;
break;
case Action::AppClose:
// TODO: implement
break;
}
}
}
self->mutex.unlock();
}
}
void ButtonControl::updatePin(std::vector<PinConfiguration>::const_reference configuration, std::vector<PinState>::reference state) {
if (tt::hal::gpio::getLevel(configuration.pin)) { // if pressed
if (state.pressState) {
// check time for long press trigger
auto time_passed = tt::kernel::getMillis() - state.pressStartTime;
if (time_passed > 500) {
// state.triggerLongPress = true;
}
} else {
state.pressStartTime = tt::kernel::getMillis();
state.pressState = true;
}
} else { // released
if (state.pressState) {
auto time_passed = tt::kernel::getMillis() - state.pressStartTime;
if (time_passed < 500) {
TT_LOG_D(TAG, "Trigger short press");
state.triggerShortPress = true;
}
state.pressState = false;
}
}
}
void ButtonControl::driverThreadMain() {
while (!shouldInterruptDriverThread()) {
if (mutex.lock(100)) {
for (int i = 0; i < pinConfigurations.size(); i++) {
updatePin(pinConfigurations[i], pinStates[i]);
}
mutex.unlock();
}
tt::kernel::delayMillis(5);
}
}
bool ButtonControl::shouldInterruptDriverThread() const {
bool interrupt = false;
if (mutex.lock(50 / portTICK_PERIOD_MS)) {
interrupt = interruptDriverThread;
mutex.unlock();
}
return interrupt;
}
void ButtonControl::startThread() {
TT_LOG_I(TAG, "Start");
mutex.lock();
interruptDriverThread = false;
driverThread = std::make_shared<tt::Thread>("ButtonControl", 4096, [this] {
driverThreadMain();
return 0;
});
driverThread->start();
mutex.unlock();
}
void ButtonControl::stopThread() {
TT_LOG_I(TAG, "Stop");
mutex.lock();
interruptDriverThread = true;
mutex.unlock();
driverThread->join();
mutex.lock();
driverThread = nullptr;
mutex.unlock();
}
bool ButtonControl::startLvgl(lv_display_t* display) {
if (deviceHandle != nullptr) {
return false;
}
startThread();
deviceHandle = lv_indev_create();
lv_indev_set_type(deviceHandle, LV_INDEV_TYPE_ENCODER);
lv_indev_set_driver_data(deviceHandle, this);
lv_indev_set_read_cb(deviceHandle, readCallback);
return true;
}
bool ButtonControl::stopLvgl() {
if (deviceHandle == nullptr) {
return false;
}
lv_indev_delete(deviceHandle);
deviceHandle = nullptr;
stopThread();
return true;
}
@@ -0,0 +1,110 @@
#pragma once
#include <Tactility/hal/encoder/EncoderDevice.h>
#include <Tactility/hal/gpio/Gpio.h>
#include <Tactility/TactilityCore.h>
class ButtonControl final : public tt::hal::encoder::EncoderDevice {
public:
enum class Event {
ShortPress,
LongPress
};
enum class Action {
UiSelectNext,
UiSelectPrevious,
UiPressSelected,
AppClose,
};
struct PinConfiguration {
tt::hal::gpio::Pin pin;
Event event;
Action action;
};
private:
struct PinState {
long pressStartTime = 0;
long pressReleaseTime = 0;
bool pressState = false;
bool triggerShortPress = false;
bool triggerLongPress = false;
};
lv_indev_t* _Nullable deviceHandle = nullptr;
std::shared_ptr<tt::Thread> driverThread;
bool interruptDriverThread = false;
tt::Mutex mutex;
std::vector<PinConfiguration> pinConfigurations;
std::vector<PinState> pinStates;
bool shouldInterruptDriverThread() const;
static void updatePin(std::vector<PinConfiguration>::const_reference value, std::vector<PinState>::reference pin_state);
void driverThreadMain();
static void readCallback(lv_indev_t* indev, lv_indev_data_t* data);
void startThread();
void stopThread();
public:
explicit ButtonControl(const std::vector<PinConfiguration>& pinConfigurations);
~ButtonControl() override;
std::string getName() const override { return "ButtonControl"; }
std::string getDescription() const override { return "ButtonControl input driver"; }
bool startLvgl(lv_display_t* display) override;
bool stopLvgl() override;
lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; }
static std::shared_ptr<ButtonControl> createOneButtonControl(tt::hal::gpio::Pin pin) {
return std::make_shared<ButtonControl>(std::vector {
PinConfiguration {
.pin = pin,
.event = Event::ShortPress,
.action = Action::UiSelectNext
},
PinConfiguration {
.pin = pin,
.event = Event::LongPress,
.action = Action::UiPressSelected
}
});
}
static std::shared_ptr<ButtonControl> createTwoButtonControl(tt::hal::gpio::Pin primaryPin, tt::hal::gpio::Pin secondaryPin) {
return std::make_shared<ButtonControl>(std::vector {
PinConfiguration {
.pin = primaryPin,
.event = Event::ShortPress,
.action = Action::UiPressSelected
},
PinConfiguration {
.pin = primaryPin,
.event = Event::LongPress,
.action = Action::AppClose
},
PinConfiguration {
.pin = secondaryPin,
.event = Event::ShortPress,
.action = Action::UiSelectNext
},
PinConfiguration {
.pin = secondaryPin,
.event = Event::LongPress,
.action = Action::UiSelectPrevious
}
});
}
};
+5
View File
@@ -0,0 +1,5 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility driver EspLcdCompat esp_lcd_st7735
)
+3
View File
@@ -0,0 +1,3 @@
# ST7735
A basic ESP32 LVGL driver for ST7735 displays.
+170
View File
@@ -0,0 +1,170 @@
#include "St7735Display.h"
#include <Tactility/Log.h>
#include <esp_lcd_panel_commands.h>
#include <esp_lcd_panel_dev.h>
#include <esp_lcd_st7735.h>
#include <esp_lvgl_port.h>
constexpr auto TAG = "ST7735";
bool St7735Display::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
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 = 1,
.lsb_first = 0,
.cs_high_active = 0
}
};
if (esp_lcd_new_panel_io_spi(configuration->spiHostDevice, &panel_io_config, &outHandle) != ESP_OK) {
TT_LOG_E(TAG, "Failed to create panel");
return false;
}
return true;
}
bool St7735Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) {
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = configuration->resetPin,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
.bits_per_pixel = 16,
.flags = {
.reset_active_high = false
},
.vendor_config = nullptr
};
if (esp_lcd_new_panel_st7735(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;
}
// Warning: it looks like LVGL rotation is broken when "gap" is set and the screen is moved to a non-default orientation
int gap_x = configuration->swapXY ? configuration->gapY : configuration->gapX;
int gap_y = configuration->swapXY ? configuration->gapX : configuration->gapY;
if (esp_lcd_panel_set_gap(panelHandle, gap_x, gap_y) != ESP_OK) {
TT_LOG_E(TAG, "Failed to set panel gap");
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_disp_on_off(panelHandle, true) != ESP_OK) {
TT_LOG_E(TAG, "Failed to turn display on");
return false;
}
return true;
}
lvgl_port_display_cfg_t St7735Display::getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) {
return lvgl_port_display_cfg_t {
.io_handle = ioHandle,
.panel_handle = panelHandle,
.control_handle = nullptr,
.buffer_size = configuration->bufferSize,
.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_RGB565,
.flags = {
.buff_dma = true,
.buff_spiram = false,
.sw_rotate = false,
.swap_bytes = true,
.full_refresh = false,
.direct_mode = false
}
};
}
/**
* Note:
* The datasheet implies this should work, but it doesn't:
* https://www.digikey.com/htmldatasheets/production/1640716/0/0/1/ILI9341-Datasheet.pdf
*
* This repo claims it only has 1 curve:
* https://github.com/brucemack/hello-ili9341
*
* I'm leaving it in as I'm not sure if it's just my hardware that's problematic.
*/
void St7735Display::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
};
auto io_handle = getIoHandle();
assert(io_handle != nullptr);
if (esp_lcd_panel_io_tx_param(io_handle, LCD_CMD_GAMSET, param, 1) != ESP_OK) {
TT_LOG_E(TAG, "Failed to set gamma");
}
}
+116
View File
@@ -0,0 +1,116 @@
#pragma once
#include "Tactility/hal/spi/Spi.h"
#include <EspLcdDisplay.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <driver/gpio.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_types.h>
#include <functional>
#include <lvgl.h>
class St7735Display final : public EspLcdDisplay {
std::shared_ptr<tt::Lock> lock;
public:
class Configuration {
public:
Configuration(
spi_host_device_t spiHostDevice,
gpio_num_t csPin,
gpio_num_t dcPin,
gpio_num_t resetPin,
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
int gapX = 0,
int gapY = 0
) : spiHostDevice(spiHostDevice),
csPin(csPin),
dcPin(dcPin),
resetPin(resetPin),
horizontalResolution(horizontalResolution),
verticalResolution(verticalResolution),
gapX(gapX),
gapY(gapY),
swapXY(swapXY),
mirrorX(mirrorX),
mirrorY(mirrorY),
invertColor(invertColor),
bufferSize(bufferSize),
touch(std::move(touch))
{
if (this->bufferSize == 0) {
this->bufferSize = horizontalResolution * verticalResolution / 10;
}
}
spi_host_device_t spiHostDevice;
gpio_num_t csPin;
gpio_num_t dcPin;
gpio_num_t resetPin = GPIO_NUM_NC;
unsigned int pixelClockFrequency = 27'000'000; // Hertz
size_t transactionQueueDepth = 10;
unsigned int horizontalResolution;
unsigned int verticalResolution;
int gapX;
int gapY;
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;
bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) override;
bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) override;
lvgl_port_display_cfg_t getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) override;
public:
explicit St7735Display(std::unique_ptr<Configuration> inConfiguration) :
EspLcdDisplay(tt::hal::spi::getLock(inConfiguration->spiHostDevice)),
configuration(std::move(inConfiguration)
) {
assert(configuration != nullptr);
assert(getLock() != nullptr);
}
std::string getName() const override { return "ST7735"; }
std::string getDescription() const override { return "ST7735 display"; }
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable getTouchDevice() override { return configuration->touch; }
void setBacklightDuty(uint8_t backlightDuty) override {
if (configuration->backlightDutyFunction != nullptr) {
configuration->backlightDutyFunction(backlightDuty);
}
}
bool supportsBacklightDuty() const override { return configuration->backlightDutyFunction != nullptr; }
void setGammaCurve(uint8_t index) override;
uint8_t getGammaCurveCount() const override { return 4; };
};
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();