Implemented LilyGO T-Lora Pager (#295)
This commit is contained in:
@@ -8,10 +8,10 @@
|
||||
* https://gitlab.com/hamishcunningham/unphonelibrary/-/blob/main/unPhone.h?ref_type=heads
|
||||
*/
|
||||
namespace registers {
|
||||
static const uint8_t CHARGE_TERMINATION = 0x05U; // Datasheet page 35: Charge end/timer cntrl
|
||||
static const uint8_t OPERATION_CONTROL = 0x07U; // Datasheet page 37: Misc operation control
|
||||
static const uint8_t STATUS = 0x08U; // Datasheet page 38: System status
|
||||
static const uint8_t VERSION = 0x0AU; // Datasheet page 38: Vendor/part/revision status
|
||||
static const uint8_t CHARGE_TERMINATION = 0x05U; // Datasheet page 35: Charge end/timer cntrl
|
||||
static const uint8_t OPERATION_CONTROL = 0x07U; // Datasheet page 37: Misc operation control
|
||||
static const uint8_t STATUS = 0x08U; // Datasheet page 38: System status
|
||||
static const uint8_t VERSION = 0x0AU; // Datasheet page 38: Vendor/part/revision status
|
||||
} // namespace registers
|
||||
|
||||
bool Bq24295::readChargeTermination(uint8_t& out) const {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "Source"
|
||||
INCLUDE_DIRS "Source"
|
||||
REQUIRES Tactility
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
# BQ27220
|
||||
|
||||
Power management: Single-Cell CEDV Fuel Gauge
|
||||
|
||||
[Datasheet](https://www.ti.com/lit/gpn/bq27220)
|
||||
[User Guide](https://www.ti.com/lit/pdf/sluubd4)
|
||||
@@ -0,0 +1,370 @@
|
||||
#include "Bq27220.h"
|
||||
#include <Tactility/Log.h>
|
||||
|
||||
#include "esp_sleep.h"
|
||||
|
||||
#define TAG "bq27220"
|
||||
|
||||
#define ARRAYSIZE(a) (sizeof(a) / sizeof(*(a)))
|
||||
|
||||
static uint8_t highByte(const uint16_t word) { return (word >> 8) & 0xFF; }
|
||||
static uint8_t lowByte(const uint16_t word) { return word & 0xFF; }
|
||||
static constexpr void swapEndianess(uint16_t &word) { word = (lowByte(word) << 8) | highByte(word); }
|
||||
|
||||
namespace registers {
|
||||
static const uint16_t SUBCMD_CTRL_STATUS = 0x0000U;
|
||||
static const uint16_t SUBCMD_DEVICE_NUMBER = 0x0001U;
|
||||
static const uint16_t SUBCMD_FW_VERSION = 0x0002U;
|
||||
static const uint16_t SUBCMD_HW_VERSION = 0x0003U;
|
||||
static const uint16_t SUBCMD_BOARD_OFFSET = 0x0009U;
|
||||
static const uint16_t SUBCMD_CC_OFFSET = 0x000AU;
|
||||
static const uint16_t SUBCMD_CC_OFFSET_SAVE = 0x000BU;
|
||||
static const uint16_t SUBCMD_OCV_CMD = 0x000CU;
|
||||
static const uint16_t SUBCMD_BAT_INSERT = 0x000DU;
|
||||
static const uint16_t SUBCMD_BAT_REMOVE = 0x000EU;
|
||||
static const uint16_t SUBCMD_SET_SNOOZE = 0x0013U;
|
||||
static const uint16_t SUBCMD_CLEAR_SNOOZE = 0x0014U;
|
||||
static const uint16_t SUBCMD_SET_PROFILE_1 = 0x0015U;
|
||||
static const uint16_t SUBCMD_SET_PROFILE_2 = 0x0016U;
|
||||
static const uint16_t SUBCMD_SET_PROFILE_3 = 0x0017U;
|
||||
static const uint16_t SUBCMD_SET_PROFILE_4 = 0x0018U;
|
||||
static const uint16_t SUBCMD_SET_PROFILE_5 = 0x0019U;
|
||||
static const uint16_t SUBCMD_SET_PROFILE_6 = 0x001AU;
|
||||
static const uint16_t SUBCMD_CAL_TOGGLE = 0x002DU;
|
||||
static const uint16_t SUBCMD_SEALED = 0x0030U;
|
||||
static const uint16_t SUBCMD_RESET = 0x0041U;
|
||||
static const uint16_t SUBCMD_EXIT_CAL = 0x0080U;
|
||||
static const uint16_t SUBCMD_ENTER_CAL = 0x0081U;
|
||||
static const uint16_t SUBCMD_ENTER_CFG_UPDATE = 0x0090U;
|
||||
static const uint16_t SUBCMD_EXIT_CFG_UPDATE_REINIT = 0x0091U;
|
||||
static const uint16_t SUBCMD_EXIT_CFG_UPDATE = 0x0092U;
|
||||
static const uint16_t SUBCMD_RETURN_TO_ROM = 0x0F00U;
|
||||
|
||||
static const uint8_t CMD_CONTROL = 0x00U;
|
||||
static const uint8_t CMD_AT_RATE = 0x02U;
|
||||
static const uint8_t CMD_AT_RATE_TIME_TO_EMPTY = 0x04U;
|
||||
static const uint8_t CMD_TEMPERATURE = 0x06U;
|
||||
static const uint8_t CMD_VOLTAGE = 0x08U;
|
||||
static const uint8_t CMD_BATTERY_STATUS = 0x0AU;
|
||||
static const uint8_t CMD_CURRENT = 0x0CU;
|
||||
static const uint8_t CMD_REMAINING_CAPACITY = 0x10U;
|
||||
static const uint8_t CMD_FULL_CHARGE_CAPACITY = 0x12U;
|
||||
static const uint8_t CMD_AVG_CURRENT = 0x14U;
|
||||
static const uint8_t CMD_TIME_TO_EMPTY = 0x16U;
|
||||
static const uint8_t CMD_TIME_TO_FULL = 0x18U;
|
||||
static const uint8_t CMD_STANDBY_CURRENT = 0x1AU;
|
||||
static const uint8_t CMD_STANDBY_TIME_TO_EMPTY = 0x1CU;
|
||||
static const uint8_t CMD_MAX_LOAD_CURRENT = 0x1EU;
|
||||
static const uint8_t CMD_MAX_LOAD_TIME_TO_EMPTY = 0x20U;
|
||||
static const uint8_t CMD_RAW_COULOMB_COUNT = 0x22U;
|
||||
static const uint8_t CMD_AVG_POWER = 0x24U;
|
||||
static const uint8_t CMD_INTERNAL_TEMPERATURE = 0x28U;
|
||||
static const uint8_t CMD_CYCLE_COUNT = 0x2AU;
|
||||
static const uint8_t CMD_STATE_OF_CHARGE = 0x2CU;
|
||||
static const uint8_t CMD_STATE_OF_HEALTH = 0x2EU;
|
||||
static const uint8_t CMD_CHARGE_VOLTAGE = 0x30U;
|
||||
static const uint8_t CMD_CHARGE_CURRENT = 0x32U;
|
||||
static const uint8_t CMD_BTP_DISCHARGE_SET = 0x34U;
|
||||
static const uint8_t CMD_BTP_CHARGE_SET = 0x36U;
|
||||
static const uint8_t CMD_OPERATION_STATUS = 0x3AU;
|
||||
static const uint8_t CMD_DESIGN_CAPACITY = 0x3CU;
|
||||
static const uint8_t CMD_SELECT_SUBCLASS = 0x3EU;
|
||||
static const uint8_t CMD_MAC_DATA = 0x40U;
|
||||
static const uint8_t CMD_MAC_DATA_SUM = 0x60U;
|
||||
static const uint8_t CMD_MAC_DATA_LEN = 0x61U;
|
||||
static const uint8_t CMD_ANALOG_COUNT = 0x79U;
|
||||
static const uint8_t CMD_RAW_CURRENT = 0x7AU;
|
||||
static const uint8_t CMD_RAW_VOLTAGE = 0x7CU;
|
||||
static const uint8_t CMD_RAW_INTERNAL_TEMPERATURE = 0x7EU;
|
||||
static const uint8_t MAC_BUFFER_START = 0x40U;
|
||||
static const uint8_t MAC_BUFFER_END = 0x5FU;
|
||||
static const uint8_t MAC_DATA_SUM = 0x60U;
|
||||
static const uint8_t MAC_DATA_LEN = 0x61U;
|
||||
static const uint8_t ROM_START = 0x3EU;
|
||||
|
||||
static const uint16_t ROM_FULL_CHARGE_CAPACITY = 0x929DU;
|
||||
static const uint16_t ROM_DESIGN_CAPACITY = 0x929FU;
|
||||
static const uint16_t ROM_OPERATION_CONFIG_A = 0x9206U;
|
||||
static const uint16_t ROM_OPERATION_CONFIG_B = 0x9208U;
|
||||
|
||||
} // namespace registers
|
||||
|
||||
bool Bq27220::configureCapacity(uint16_t designCapacity, uint16_t fullChargeCapacity) {
|
||||
return performConfigUpdate([this, designCapacity, fullChargeCapacity]() {
|
||||
// Set the design capacity
|
||||
if (!writeConfig16(registers::ROM_DESIGN_CAPACITY, designCapacity)) {
|
||||
TT_LOG_E(TAG, "Failed to set design capacity!");
|
||||
return false;
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
|
||||
// Set full charge capacity
|
||||
if (!writeConfig16(registers::ROM_FULL_CHARGE_CAPACITY, fullChargeCapacity)) {
|
||||
TT_LOG_E(TAG, "Failed to set full charge capacity!");
|
||||
return false;
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
bool Bq27220::getVoltage(uint16_t &value) {
|
||||
if (readRegister16(registers::CMD_VOLTAGE, value)) {
|
||||
swapEndianess(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bq27220::getCurrent(int16_t &value) {
|
||||
uint16_t u16 = 0;
|
||||
if (readRegister16(registers::CMD_CURRENT, u16)) {
|
||||
swapEndianess(u16);
|
||||
value = (int16_t)u16;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bq27220::getBatteryStatus(Bq27220::BatteryStatus &batt_sta) {
|
||||
if (readRegister16(registers::CMD_BATTERY_STATUS, batt_sta.full)) {
|
||||
swapEndianess(batt_sta.full);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bq27220::getOperationStatus(OperationStatus &oper_sta) {
|
||||
if (readRegister16(registers::CMD_OPERATION_STATUS, oper_sta.full)) {
|
||||
swapEndianess(oper_sta.full);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bq27220::getTemperature(uint16_t &value) {
|
||||
if (readRegister16(registers::CMD_TEMPERATURE, value)) {
|
||||
swapEndianess(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bq27220::getFullChargeCapacity(uint16_t &value) {
|
||||
if (readRegister16(registers::CMD_FULL_CHARGE_CAPACITY, value)) {
|
||||
swapEndianess(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bq27220::getDesignCapacity(uint16_t &value) {
|
||||
if (readRegister16(registers::CMD_DESIGN_CAPACITY, value)) {
|
||||
swapEndianess(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bq27220::getRemainingCapacity(uint16_t &value) {
|
||||
if (readRegister16(registers::CMD_REMAINING_CAPACITY, value)) {
|
||||
swapEndianess(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bq27220::getStateOfCharge(uint16_t &value) {
|
||||
if (readRegister16(registers::CMD_STATE_OF_CHARGE, value)) {
|
||||
swapEndianess(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bq27220::getStateOfHealth(uint16_t &value) {
|
||||
if (readRegister16(registers::CMD_STATE_OF_HEALTH, value)) {
|
||||
swapEndianess(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bq27220::getChargeVoltageMax(uint16_t &value) {
|
||||
if (readRegister16(registers::CMD_CHARGE_VOLTAGE, value)) {
|
||||
swapEndianess(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bq27220::unsealDevice() {
|
||||
uint8_t cmd1[] = {0x00, 0x14, 0x04};
|
||||
if (!write(cmd1, ARRAYSIZE(cmd1))) {
|
||||
return false;
|
||||
}
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
uint8_t cmd2[] = {0x00, 0x72, 0x36};
|
||||
if (!write(cmd2, ARRAYSIZE(cmd2))) {
|
||||
return false;
|
||||
}
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bq27220::unsealFullAccess()
|
||||
{
|
||||
uint8_t buffer[3];
|
||||
buffer[0] = 0x00;
|
||||
buffer[1] = lowByte((accessKey >> 24));
|
||||
buffer[2] = lowByte((accessKey >> 16));
|
||||
if (!write(buffer, ARRAYSIZE(buffer))) {
|
||||
return false;
|
||||
}
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
buffer[1] = lowByte((accessKey >> 8));
|
||||
buffer[2] = lowByte((accessKey));
|
||||
if (!write(buffer, ARRAYSIZE(buffer))) {
|
||||
return false;
|
||||
}
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bq27220::exitSealMode() {
|
||||
return sendSubCommand(registers::SUBCMD_SEALED);
|
||||
}
|
||||
|
||||
bool Bq27220::sendSubCommand(uint16_t subCmd, bool waitConfirm)
|
||||
{
|
||||
uint8_t buffer[3];
|
||||
buffer[0] = 0x00;
|
||||
buffer[1] = lowByte(subCmd);
|
||||
buffer[2] = highByte(subCmd);
|
||||
if (!write(buffer, ARRAYSIZE(buffer))) {
|
||||
return false;
|
||||
}
|
||||
if (!waitConfirm) {
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
return true;
|
||||
}
|
||||
constexpr uint8_t statusReg = 0x00;
|
||||
int waitCount = 20;
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
while (waitCount--) {
|
||||
writeRead(&statusReg, 1, buffer, 2);
|
||||
uint16_t *value = reinterpret_cast<uint16_t *>(buffer);
|
||||
if (*value == 0xFFA5) {
|
||||
return true;
|
||||
}
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
}
|
||||
TT_LOG_E(TAG, "Subcommand x%X failed!", subCmd);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bq27220::writeConfig16(uint16_t address, uint16_t value) {
|
||||
constexpr uint8_t fixedDataLength = 0x06;
|
||||
const uint8_t msbAccessValue = highByte(address);
|
||||
const uint8_t lsbAccessValue = lowByte(address);
|
||||
|
||||
// Write to access the MSB of Capacity
|
||||
writeRegister8(registers::ROM_START, msbAccessValue);
|
||||
|
||||
// Write to access the LSB of Capacity
|
||||
writeRegister8(registers::ROM_START + 1, lsbAccessValue);
|
||||
|
||||
// Write two Capacity bytes starting from 0x40
|
||||
uint8_t valueMsb = highByte(value);
|
||||
uint8_t valueLsb = lowByte(value);
|
||||
uint8_t configRaw[] = {valueMsb, valueLsb};
|
||||
writeRegister(registers::MAC_BUFFER_START, configRaw, 2);
|
||||
// Calculate new checksum
|
||||
uint8_t checksum = 0xFF - ((msbAccessValue + lsbAccessValue + valueMsb + valueLsb) & 0xFF);
|
||||
|
||||
// Write new checksum (0x60)
|
||||
writeRegister8(registers::MAC_DATA_SUM, checksum);
|
||||
|
||||
// Write the block length
|
||||
writeRegister8(registers::MAC_DATA_LEN, fixedDataLength);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bq27220::configPreamble(bool &isSealed) {
|
||||
int timeout = 0;
|
||||
OperationStatus status;
|
||||
|
||||
// Check access settings
|
||||
if(!getOperationStatus(status)) {
|
||||
TT_LOG_E(TAG, "Cannot read initial operation status!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (status.reg.SEC == OperationStatusSecSealed) {
|
||||
isSealed = true;
|
||||
if (!unsealDevice()) {
|
||||
TT_LOG_E(TAG, "Unsealing device failure!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (status.reg.SEC != OperationStatusSecFull) {
|
||||
if (!unsealFullAccess()) {
|
||||
TT_LOG_E(TAG, "Unsealing full access failure!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Send ENTER_CFG_UPDATE command (0x0090)
|
||||
if (!sendSubCommand(registers::SUBCMD_ENTER_CFG_UPDATE)) {
|
||||
TT_LOG_E(TAG, "Config Update Subcommand failure!");
|
||||
}
|
||||
|
||||
// Confirm CFUPDATE mode by polling the OperationStatus() register until Bit 2 is set.
|
||||
bool isConfigUpdate = false;
|
||||
for (timeout = 30; timeout; --timeout) {
|
||||
getOperationStatus(status);
|
||||
if (status.reg.CFGUPDATE) {
|
||||
isConfigUpdate = true;
|
||||
break;
|
||||
}
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
}
|
||||
if (!isConfigUpdate) {
|
||||
TT_LOG_E(TAG, "Update Mode timeout, maybe the access key for full permissions is invalid!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bq27220::configEpilouge(const bool isSealed) {
|
||||
int timeout = 0;
|
||||
OperationStatus status;
|
||||
|
||||
// Exit CFUPDATE mode by sending the EXIT_CFG_UPDATE_REINIT (0x0091) or EXIT_CFG_UPDATE (0x0092) command
|
||||
sendSubCommand(registers::SUBCMD_EXIT_CFG_UPDATE_REINIT);
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
|
||||
// Confirm that CFUPDATE mode has been exited by polling the OperationStatus() register until bit 2 is cleared
|
||||
for (timeout = 60; timeout; --timeout) {
|
||||
getOperationStatus(status);
|
||||
if (!status.reg.CFGUPDATE) {
|
||||
break;
|
||||
}
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
}
|
||||
if (timeout == 0) {
|
||||
TT_LOG_E(TAG, "Timed out waiting to exit update mode.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the device was previously in SEALED state, return to SEALED mode by sending the Control(0x0030) subcommand
|
||||
if (isSealed) {
|
||||
TT_LOG_D(TAG, "Restore Safe Mode!");
|
||||
exitSealMode();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/hal/i2c/I2cDevice.h>
|
||||
|
||||
#define BQ27220_ADDRESS 0x55
|
||||
|
||||
class Bq27220 final : public tt::hal::i2c::I2cDevice {
|
||||
|
||||
private:
|
||||
uint32_t accessKey;
|
||||
|
||||
bool unsealDevice();
|
||||
bool unsealFullAccess();
|
||||
bool exitSealMode();
|
||||
bool sendSubCommand(uint16_t subCmd, bool waitConfirm = false);
|
||||
bool writeConfig16(uint16_t address, uint16_t value);
|
||||
bool configPreamble(bool &isSealed);
|
||||
bool configEpilouge(const bool isSealed);
|
||||
|
||||
template<typename T>
|
||||
bool performConfigUpdate(T configUpdateFunc)
|
||||
{
|
||||
bool isSealed = false;
|
||||
|
||||
if (!configPreamble(isSealed)) {
|
||||
return false;
|
||||
}
|
||||
bool result = configUpdateFunc();
|
||||
configEpilouge(isSealed);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public:
|
||||
// Register structures lifted from
|
||||
// https://github.com/Xinyuan-LilyGO/T-Deck-Pro/blob/master/lib/BQ27220/bq27220.h
|
||||
// Copyright (c) 2025 Liygo / Shenzhen Xinyuan Electronic Technology Co., Ltd
|
||||
|
||||
union BatteryStatus {
|
||||
struct
|
||||
{
|
||||
// Low byte, Low bit first
|
||||
uint16_t DSG : 1; /**< The device is in DISCHARGE */
|
||||
uint16_t SYSDWN : 1; /**< System down bit indicating the system should shut down */
|
||||
uint16_t TDA : 1; /**< Terminate Discharge Alarm */
|
||||
uint16_t BATTPRES : 1; /**< Battery Present detected */
|
||||
uint16_t AUTH_GD : 1; /**< Detect inserted battery */
|
||||
uint16_t OCVGD : 1; /**< Good OCV measurement taken */
|
||||
uint16_t TCA : 1; /**< Terminate Charge Alarm */
|
||||
uint16_t RSVD : 1; /**< Reserved */
|
||||
// High byte, Low bit first
|
||||
uint16_t CHGING : 1; /**< Charge inhibit */
|
||||
uint16_t FC : 1; /**< Full-charged is detected */
|
||||
uint16_t OTD : 1; /**< Overtemperature in discharge condition is detected */
|
||||
uint16_t OTC : 1; /**< Overtemperature in charge condition is detected */
|
||||
uint16_t SLEEP : 1; /**< Device is operating in SLEEP mode when set */
|
||||
uint16_t OCVFALL : 1; /**< Status bit indicating that the OCV reading failed due to current */
|
||||
uint16_t OCVCOMP : 1; /**< An OCV measurement update is complete */
|
||||
uint16_t FD : 1; /**< Full-discharge is detected */
|
||||
} reg;
|
||||
uint16_t full;
|
||||
};
|
||||
|
||||
enum OperationStatusSec {
|
||||
OperationStatusSecSealed = 0b11,
|
||||
OperationStatusSecUnsealed = 0b10,
|
||||
OperationStatusSecFull = 0b01,
|
||||
};
|
||||
|
||||
union OperationStatus {
|
||||
struct
|
||||
{
|
||||
// Low byte, Low bit first
|
||||
bool CALMD : 1; /**< Calibration mode enabled */
|
||||
uint8_t SEC : 2; /**< Current security access */
|
||||
bool EDV2 : 1; /**< EDV2 threshold exceeded */
|
||||
bool VDQ : 1; /**< Indicates if Current discharge cycle is NOT qualified or qualified for an FCC updated */
|
||||
bool INITCOMP : 1; /**< gauge initialization is complete */
|
||||
bool SMTH : 1; /**< RemainingCapacity is scaled by smooth engine */
|
||||
bool BTPINT : 1; /**< BTP threshold has been crossed */
|
||||
// High byte, Low bit first
|
||||
uint8_t RSVD1 : 2; /**< Reserved */
|
||||
bool CFGUPDATE : 1; /**< Gauge is in CONFIG UPDATE mode */
|
||||
uint8_t RSVD0 : 5; /**< Reserved */
|
||||
} reg;
|
||||
uint16_t full;
|
||||
};
|
||||
|
||||
std::string getName() const final { return "BQ27220"; }
|
||||
|
||||
std::string getDescription() const final { return "I2C-controlled CEDV battery fuel gauge"; }
|
||||
|
||||
explicit Bq27220(i2c_port_t port) : I2cDevice(port, BQ27220_ADDRESS), accessKey(0xFFFFFFFF) {}
|
||||
|
||||
bool configureCapacity(uint16_t designCapacity, uint16_t fullChargeCapacity);
|
||||
bool getVoltage(uint16_t &value);
|
||||
bool getCurrent(int16_t &value);
|
||||
bool getBatteryStatus(BatteryStatus &batt_sta);
|
||||
bool getOperationStatus(OperationStatus &oper_sta);
|
||||
bool getTemperature(uint16_t &value);
|
||||
bool getFullChargeCapacity(uint16_t &value);
|
||||
bool getDesignCapacity(uint16_t &value);
|
||||
bool getRemainingCapacity(uint16_t &value);
|
||||
bool getStateOfCharge(uint16_t &value);
|
||||
bool getStateOfHealth(uint16_t &value);
|
||||
bool getChargeVoltageMax(uint16_t &value);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "Source"
|
||||
INCLUDE_DIRS "Source"
|
||||
REQUIRES Tactility esp_lvgl_port esp_lcd_st7796 driver
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
# ST7796
|
||||
|
||||
A basic ESP32 LVGL driver for ST7796 displays.
|
||||
@@ -0,0 +1,210 @@
|
||||
#include "St7796Display.h"
|
||||
|
||||
#include <Tactility/Log.h>
|
||||
|
||||
#include <esp_lcd_panel_commands.h>
|
||||
#include <esp_lcd_panel_dev.h>
|
||||
#include <esp_lcd_st7796.h>
|
||||
#include <esp_lvgl_port.h>
|
||||
|
||||
#define TAG "st7796"
|
||||
|
||||
bool St7796Display::start() {
|
||||
TT_LOG_I(TAG, "Starting");
|
||||
|
||||
const esp_lcd_panel_io_spi_config_t panel_io_config = {
|
||||
.cs_gpio_num = configuration->csPin,
|
||||
.dc_gpio_num = configuration->dcPin,
|
||||
.spi_mode = 0,
|
||||
.pclk_hz = configuration->pixelClockFrequency,
|
||||
.trans_queue_depth = configuration->transactionQueueDepth,
|
||||
.on_color_trans_done = nullptr,
|
||||
.user_ctx = nullptr,
|
||||
.lcd_cmd_bits = 8,
|
||||
.lcd_param_bits = 8,
|
||||
.cs_ena_pretrans = 0,
|
||||
.cs_ena_posttrans = 0,
|
||||
.flags = {
|
||||
.dc_high_on_cmd = 0,
|
||||
.dc_low_on_data = 0,
|
||||
.dc_low_on_param = 0,
|
||||
.octal_mode = 0,
|
||||
.quad_mode = 0,
|
||||
.sio_mode = 0,
|
||||
.lsb_first = 0,
|
||||
.cs_high_active = 0
|
||||
}
|
||||
};
|
||||
|
||||
if (esp_lcd_new_panel_io_spi(configuration->spiBusHandle, &panel_io_config, &ioHandle) != ESP_OK) {
|
||||
TT_LOG_E(TAG, "Failed to create panel");
|
||||
return false;
|
||||
}
|
||||
|
||||
static const st7796_lcd_init_cmd_t lcd_init_cmds[] = {
|
||||
{0x01, (uint8_t[]) {0x00}, 0, 120},
|
||||
{0x11, (uint8_t[]) {0x00}, 0, 120},
|
||||
{0xF0, (uint8_t[]) {0xC3}, 1, 0},
|
||||
{0xF0, (uint8_t[]) {0xC3}, 1, 0},
|
||||
{0xF0, (uint8_t[]) {0x96}, 1, 0},
|
||||
{0x36, (uint8_t[]) {0x58}, 1, 0},
|
||||
{0x3A, (uint8_t[]) {0x55}, 1, 0},
|
||||
{0xB4, (uint8_t[]) {0x01}, 1, 0},
|
||||
{0xB6, (uint8_t[]) {0x80, 0x02, 0x3B}, 3, 0},
|
||||
{0xE8, (uint8_t[]) {0x40, 0x8A, 0x00, 0x00, 0x29, 0x19, 0xA5, 0x33}, 8, 0},
|
||||
{0xC1, (uint8_t[]) {0x06}, 1, 0},
|
||||
{0xC2, (uint8_t[]) {0xA7}, 1, 0},
|
||||
{0xC5, (uint8_t[]) {0x18}, 1, 0},
|
||||
{0xE0, (uint8_t[]) {0xF0, 0x09, 0x0b, 0x06, 0x04, 0x15, 0x2F, 0x54, 0x42, 0x3C, 0x17, 0x14, 0x18, 0x1B}, 15, 0},
|
||||
{0xE1, (uint8_t[]) {0xE0, 0x09, 0x0b, 0x06, 0x04, 0x03, 0x2B, 0x43, 0x42, 0x3B, 0x16, 0x14, 0x17, 0x1B}, 15, 120},
|
||||
{0xF0, (uint8_t[]) {0x3C}, 1, 0},
|
||||
{0xF0, (uint8_t[]) {0x69}, 1, 0},
|
||||
{0x21, (uint8_t[]) {0x00}, 1, 0},
|
||||
{0x29, (uint8_t[]) {0x00}, 1, 0},
|
||||
};
|
||||
|
||||
st7796_vendor_config_t vendor_config = {
|
||||
// Uncomment these lines if use custom initialization commands
|
||||
.init_cmds = lcd_init_cmds,
|
||||
.init_cmds_size = sizeof(lcd_init_cmds) / sizeof(st7796_lcd_init_cmd_t),
|
||||
};
|
||||
|
||||
|
||||
const esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = configuration->resetPin, // Set to -1 if not use
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
.color_space = ESP_LCD_COLOR_SPACE_RGB,
|
||||
#else
|
||||
.color_space = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
|
||||
#endif
|
||||
.bits_per_pixel = 16,
|
||||
.vendor_config = &vendor_config
|
||||
};
|
||||
/*
|
||||
const esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = configuration->resetPin,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
|
||||
.bits_per_pixel = 16,
|
||||
.flags = {
|
||||
.reset_active_high = false
|
||||
},
|
||||
.vendor_config = nullptr
|
||||
};
|
||||
*/
|
||||
if (esp_lcd_new_panel_st7796(ioHandle, &panel_config, &panelHandle) != ESP_OK) {
|
||||
TT_LOG_E(TAG, "Failed to create panel");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_lcd_panel_reset(panelHandle) != ESP_OK) {
|
||||
TT_LOG_E(TAG, "Failed to reset panel");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_lcd_panel_init(panelHandle) != ESP_OK) {
|
||||
TT_LOG_E(TAG, "Failed to init panel");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_lcd_panel_invert_color(panelHandle, configuration->invertColor) != ESP_OK) {
|
||||
TT_LOG_E(TAG, "Failed to set panel to invert");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_lcd_panel_swap_xy(panelHandle, configuration->swapXY) != ESP_OK) {
|
||||
TT_LOG_E(TAG, "Failed to swap XY ");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_lcd_panel_mirror(panelHandle, configuration->mirrorX, configuration->mirrorY) != ESP_OK) {
|
||||
TT_LOG_E(TAG, "Failed to set panel to mirror");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_lcd_panel_set_gap(panelHandle, configuration->gapX, configuration->gapY) != ESP_OK) {
|
||||
TT_LOG_E(TAG, "Failed to set panel gap");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_lcd_panel_disp_on_off(panelHandle, true) != ESP_OK) {
|
||||
TT_LOG_E(TAG, "Failed to turn display on");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t buffer_size;
|
||||
if (configuration->bufferSize == 0) {
|
||||
buffer_size = configuration->horizontalResolution * configuration->verticalResolution / 10;
|
||||
} else {
|
||||
buffer_size = configuration->bufferSize;
|
||||
}
|
||||
|
||||
const lvgl_port_display_cfg_t disp_cfg = {
|
||||
.io_handle = ioHandle,
|
||||
.panel_handle = panelHandle,
|
||||
.control_handle = nullptr,
|
||||
.buffer_size = buffer_size,
|
||||
.double_buffer = false,
|
||||
.trans_size = 0,
|
||||
.hres = configuration->horizontalResolution,
|
||||
.vres = configuration->verticalResolution,
|
||||
.monochrome = false,
|
||||
.rotation = {
|
||||
.swap_xy = configuration->swapXY,
|
||||
.mirror_x = configuration->mirrorX,
|
||||
.mirror_y = configuration->mirrorY,
|
||||
},
|
||||
.color_format = LV_COLOR_FORMAT_NATIVE,
|
||||
.flags = {.buff_dma = true, .buff_spiram = false, .sw_rotate = false, .swap_bytes = true, .full_refresh = false, .direct_mode = false}
|
||||
};
|
||||
|
||||
displayHandle = lvgl_port_add_disp(&disp_cfg);
|
||||
|
||||
TT_LOG_I(TAG, "Finished");
|
||||
return displayHandle != nullptr;
|
||||
}
|
||||
|
||||
bool St7796Display::stop() {
|
||||
assert(displayHandle != nullptr);
|
||||
|
||||
lvgl_port_remove_disp(displayHandle);
|
||||
|
||||
if (esp_lcd_panel_del(panelHandle) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_lcd_panel_io_del(ioHandle) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
displayHandle = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
void St7796Display::setGammaCurve(uint8_t index) {
|
||||
uint8_t gamma_curve;
|
||||
switch (index) {
|
||||
case 0:
|
||||
gamma_curve = 0x01;
|
||||
break;
|
||||
case 1:
|
||||
gamma_curve = 0x04;
|
||||
break;
|
||||
case 2:
|
||||
gamma_curve = 0x02;
|
||||
break;
|
||||
case 3:
|
||||
gamma_curve = 0x08;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
const uint8_t param[] = {
|
||||
gamma_curve
|
||||
};
|
||||
|
||||
/*if (esp_lcd_panel_io_tx_param(ioHandle , LCD_CMD_GAMSET, param, 1) != ESP_OK) {
|
||||
TT_LOG_E(TAG, "Failed to set gamma");
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tactility/hal/display/DisplayDevice.h"
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_types.h>
|
||||
#include <functional>
|
||||
#include <lvgl.h>
|
||||
|
||||
class St7796Display final : public tt::hal::display::DisplayDevice {
|
||||
|
||||
public:
|
||||
|
||||
class Configuration {
|
||||
|
||||
public:
|
||||
|
||||
Configuration(
|
||||
esp_lcd_spi_bus_handle_t spi_bus_handle,
|
||||
gpio_num_t csPin,
|
||||
gpio_num_t dcPin,
|
||||
unsigned int horizontalResolution,
|
||||
unsigned int verticalResolution,
|
||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch,
|
||||
bool swapXY = false,
|
||||
bool mirrorX = false,
|
||||
bool mirrorY = false,
|
||||
bool invertColor = false,
|
||||
unsigned int gapX = 0,
|
||||
unsigned int gapY = 0,
|
||||
uint32_t bufferSize = 0 // Size in pixel count. 0 means default, which is 1/10 of the screen size
|
||||
) : spiBusHandle(spi_bus_handle),
|
||||
csPin(csPin),
|
||||
dcPin(dcPin),
|
||||
horizontalResolution(horizontalResolution),
|
||||
verticalResolution(verticalResolution),
|
||||
swapXY(swapXY),
|
||||
mirrorX(mirrorX),
|
||||
mirrorY(mirrorY),
|
||||
invertColor(invertColor),
|
||||
gapX(gapX),
|
||||
gapY(gapY),
|
||||
bufferSize(bufferSize),
|
||||
touch(std::move(touch)) {}
|
||||
|
||||
esp_lcd_spi_bus_handle_t spiBusHandle;
|
||||
gpio_num_t csPin;
|
||||
gpio_num_t dcPin;
|
||||
gpio_num_t resetPin = GPIO_NUM_NC;
|
||||
unsigned int pixelClockFrequency = 80'000'000; // Hertz
|
||||
size_t transactionQueueDepth = 2;
|
||||
unsigned int horizontalResolution;
|
||||
unsigned int verticalResolution;
|
||||
bool swapXY = false;
|
||||
bool mirrorX = false;
|
||||
bool mirrorY = false;
|
||||
bool invertColor = false;
|
||||
unsigned int gapX = 0;
|
||||
unsigned int gapY = 0;
|
||||
uint32_t bufferSize = 0; // Size in pixel count. 0 means default, which is 1/10 of the screen size
|
||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
|
||||
std::function<void(uint8_t)> _Nullable backlightDutyFunction = nullptr;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
std::unique_ptr<Configuration> configuration;
|
||||
esp_lcd_panel_io_handle_t ioHandle = nullptr;
|
||||
esp_lcd_panel_handle_t panelHandle = nullptr;
|
||||
lv_display_t* displayHandle = nullptr;
|
||||
|
||||
public:
|
||||
|
||||
explicit St7796Display(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
|
||||
assert(configuration != nullptr);
|
||||
}
|
||||
|
||||
std::string getName() const final { return "ST7796"; }
|
||||
std::string getDescription() const final { return "ST7796 display"; }
|
||||
|
||||
bool start() final;
|
||||
|
||||
bool stop() final;
|
||||
|
||||
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable createTouch() final { return configuration->touch; }
|
||||
|
||||
void setBacklightDuty(uint8_t backlightDuty) final {
|
||||
if (configuration->backlightDutyFunction != nullptr) {
|
||||
configuration->backlightDutyFunction(backlightDuty);
|
||||
}
|
||||
}
|
||||
|
||||
void setGammaCurve(uint8_t index) final;
|
||||
uint8_t getGammaCurveCount() const final { return 4; };
|
||||
|
||||
bool supportsBacklightDuty() const final { return configuration->backlightDutyFunction != nullptr; }
|
||||
|
||||
lv_display_t* _Nullable getLvglDisplay() const final { return displayHandle; }
|
||||
};
|
||||
|
||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "Source"
|
||||
INCLUDE_DIRS "Source"
|
||||
REQUIRES Tactility
|
||||
)
|
||||
@@ -0,0 +1,18 @@
|
||||
Copyright 2023 Anthony DiGirolamo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the “Software”), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,4 @@
|
||||
# TCA8418 I2C Controlled Keypad Scan IC With Integrated ESD Protection
|
||||
|
||||
[Datasheet](https://www.ti.com/lit/ds/symlink/tca8418.pdf?ts=1751500237439)
|
||||
[Original implementation](https://github.com/AnthonyDiGirolamo/i2c-thumb-keyboard/tree/master) by Anthony DiGirolamo
|
||||
@@ -0,0 +1,202 @@
|
||||
#include "Tca8418.h"
|
||||
#include <Tactility/Log.h>
|
||||
|
||||
#define TAG "tca8418"
|
||||
|
||||
namespace registers {
|
||||
static const uint8_t CFG = 0x01U;
|
||||
static const uint8_t KP_GPIO1 = 0x1DU;
|
||||
static const uint8_t KP_GPIO2 = 0x1EU;
|
||||
static const uint8_t KP_GPIO3 = 0x1FU;
|
||||
|
||||
static const uint8_t KEY_EVENT_A = 0x04U;
|
||||
static const uint8_t KEY_EVENT_B = 0x05U;
|
||||
static const uint8_t KEY_EVENT_C = 0x06U;
|
||||
static const uint8_t KEY_EVENT_D = 0x07U;
|
||||
static const uint8_t KEY_EVENT_E = 0x08U;
|
||||
static const uint8_t KEY_EVENT_F = 0x09U;
|
||||
static const uint8_t KEY_EVENT_G = 0x0AU;
|
||||
static const uint8_t KEY_EVENT_H = 0x0BU;
|
||||
static const uint8_t KEY_EVENT_I = 0x0CU;
|
||||
static const uint8_t KEY_EVENT_J = 0x0DU;
|
||||
} // namespace registers
|
||||
|
||||
|
||||
void Tca8418::init(uint8_t numrows, uint8_t numcols) {
|
||||
/*
|
||||
* | ADDRESS | REGISTER NAME | REGISTER DESCRIPTION | BIT7 | BIT6 | BIT5 | BIT4 | BIT3 | BIT2 | BIT1 | BIT0 |
|
||||
* |---------+---------------+----------------------+------+------+------+------+------+------+------+------|
|
||||
* | 0x1D | KP_GPIO1 | Keypad/GPIO Select 1 | ROW7 | ROW6 | ROW5 | ROW4 | ROW3 | ROW2 | ROW1 | ROW0 |
|
||||
* | 0x1E | KP_GPIO2 | Keypad/GPIO Select 2 | COL7 | COL6 | COL5 | COL4 | COL3 | COL2 | COL1 | COL0 |
|
||||
* | 0x1F | KP_GPIO3 | Keypad/GPIO Select 3 | N/A | N/A | N/A | N/A | N/A | N/A | COL9 | COL8 |
|
||||
*/
|
||||
|
||||
num_rows = numrows;
|
||||
num_cols = numcols;
|
||||
|
||||
// everything enabled in key scan mode
|
||||
uint8_t enabled_rows = 0x3F;
|
||||
uint16_t enabled_cols = 0x3FF;
|
||||
|
||||
writeRegister8(registers::KP_GPIO1, enabled_rows);
|
||||
writeRegister8(registers::KP_GPIO2, (uint8_t)(0xFF & enabled_cols));
|
||||
writeRegister8(registers::KP_GPIO3, (uint8_t)(0x03 & (enabled_cols >> 8)));
|
||||
|
||||
/*
|
||||
* BIT: NAME
|
||||
*
|
||||
* 7: AI
|
||||
* Auto-increment for read and write operations; See below table for more information
|
||||
* 0 = disabled
|
||||
* 1 = enabled
|
||||
*
|
||||
* 6: GPI_E_CFG
|
||||
* GPI event mode configuration
|
||||
* 0 = GPI events are tracked when keypad is locked
|
||||
* 1 = GPI events are not tracked when keypad is locked
|
||||
*
|
||||
* 5: OVR_FLOW_M
|
||||
* Overflow mode
|
||||
* 0 = disabled; Overflow data is lost
|
||||
* 1 = enabled; Overflow data shifts with last event pushing first event out
|
||||
*
|
||||
* 4: INT_CFG
|
||||
* Interrupt configuration
|
||||
* 0 = processor interrupt remains asserted (or low) if host tries to clear interrupt while there is
|
||||
* still a pending key press, key release or GPI interrupt
|
||||
* 1 = processor interrupt is deasserted for 50 μs and reassert with pending interrupts
|
||||
*
|
||||
* 3: OVR_FLOW_IEN
|
||||
* Overflow interrupt enable
|
||||
* 0 = disabled; INT is not asserted if the FIFO overflows
|
||||
* 1 = enabled; INT becomes asserted if the FIFO overflows
|
||||
*
|
||||
* 2: K_LCK_IEN
|
||||
* Keypad lock interrupt enable
|
||||
* 0 = disabled; INT is not asserted after a correct unlock key sequence
|
||||
* 1 = enabled; INT becomes asserted after a correct unlock key sequence
|
||||
*
|
||||
* 1: GPI_IEN
|
||||
* GPI interrupt enable to host processor
|
||||
* 0 = disabled; INT is not asserted for a change on a GPI
|
||||
* 1 = enabled; INT becomes asserted for a change on a GPI
|
||||
*
|
||||
* 0: KE_IEN
|
||||
* Key events interrupt enable to host processor
|
||||
* 0 = disabled; INT is not asserted when a key event occurs
|
||||
* 1 = enabled; INT becomes asserted when a key event occurs
|
||||
*/
|
||||
|
||||
// 10111001 xB9 -- fifo overflow enabled
|
||||
// 10011001 x99 -- fifo overflow disabled
|
||||
writeRegister8(registers::CFG, 0x99);
|
||||
|
||||
clear_released_list();
|
||||
clear_pressed_list();
|
||||
}
|
||||
|
||||
bool Tca8418::update() {
|
||||
last_update_micros = this_update_micros;
|
||||
uint8_t key_code, key_down, key_event, key_row, key_col;
|
||||
|
||||
key_event = get_key_event();
|
||||
// TODO: read gpio R7/R6 status? 0x14 bits 7&6
|
||||
// read(0x14, &new_keycode)
|
||||
|
||||
// TODO: use tick function to get an update delta time
|
||||
this_update_micros = 0;
|
||||
delta_micros = this_update_micros - last_update_micros;
|
||||
|
||||
if (key_event > 0) {
|
||||
key_code = key_event & 0x7F;
|
||||
key_down = (key_event & 0x80) >> 7;
|
||||
key_row = key_code / num_cols;
|
||||
key_col = key_code % num_cols;
|
||||
|
||||
// always clear the released list
|
||||
clear_released_list();
|
||||
|
||||
if (key_down) {
|
||||
add_pressed_key(key_row, key_col);
|
||||
// TODO reject ghosts (assume multiple key presses with the same hold time are ghosts.)
|
||||
|
||||
} else {
|
||||
add_released_key(key_row, key_col);
|
||||
remove_pressed_key(key_row, key_col);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Increment hold times for pressed keys
|
||||
for (int i = 0; i < pressed_key_count; i++) {
|
||||
pressed_list[i].hold_time += delta_micros;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void Tca8418::add_pressed_key(uint8_t row, uint8_t col) {
|
||||
if (pressed_key_count >= KEY_EVENT_LIST_SIZE)
|
||||
return;
|
||||
|
||||
pressed_list[pressed_key_count].row = row;
|
||||
pressed_list[pressed_key_count].col = col;
|
||||
pressed_list[pressed_key_count].hold_time = 0;
|
||||
pressed_key_count++;
|
||||
}
|
||||
|
||||
void Tca8418::add_released_key(uint8_t row, uint8_t col) {
|
||||
if (released_key_count >= KEY_EVENT_LIST_SIZE)
|
||||
return;
|
||||
|
||||
released_key_count++;
|
||||
released_list[0].row = row;
|
||||
released_list[0].col = col;
|
||||
}
|
||||
|
||||
void Tca8418::remove_pressed_key(uint8_t row, uint8_t col) {
|
||||
if (pressed_key_count == 0)
|
||||
return;
|
||||
|
||||
// delete the pressed key
|
||||
for (int i = 0; i < pressed_key_count; i++) {
|
||||
if (pressed_list[i].row == row &&
|
||||
pressed_list[i].col == col) {
|
||||
// shift remaining keys left one index
|
||||
for (int j = i; i < pressed_key_count; j++) {
|
||||
if (j == KEY_EVENT_LIST_SIZE - 1)
|
||||
break;
|
||||
pressed_list[j].row = pressed_list[j + 1].row;
|
||||
pressed_list[j].col = pressed_list[j + 1].col;
|
||||
pressed_list[j].hold_time = pressed_list[j + 1].hold_time;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
pressed_key_count--;
|
||||
}
|
||||
|
||||
void Tca8418::clear_pressed_list() {
|
||||
for (int i = 0; i < KEY_EVENT_LIST_SIZE; i++) {
|
||||
pressed_list[i].row = 255;
|
||||
pressed_list[i].col = 255;
|
||||
}
|
||||
pressed_key_count = 0;
|
||||
}
|
||||
|
||||
void Tca8418::clear_released_list() {
|
||||
for (int i = 0; i < KEY_EVENT_LIST_SIZE; i++) {
|
||||
released_list[i].row = 255;
|
||||
released_list[i].col = 255;
|
||||
}
|
||||
released_key_count = 0;
|
||||
}
|
||||
|
||||
uint8_t Tca8418::get_key_event() {
|
||||
uint8_t new_keycode = 0;
|
||||
|
||||
readRegister8(registers::KEY_EVENT_A, new_keycode);
|
||||
return new_keycode;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <Tactility/hal/i2c/I2cDevice.h>
|
||||
|
||||
#define TCA8418_ADDRESS 0x34U
|
||||
#define KEY_EVENT_LIST_SIZE 10
|
||||
|
||||
class Tca8418 final : public tt::hal::i2c::I2cDevice {
|
||||
|
||||
private:
|
||||
|
||||
uint8_t tca8418_address;
|
||||
uint32_t last_update_micros;
|
||||
uint32_t this_update_micros;
|
||||
|
||||
uint8_t new_pressed_keys_count;
|
||||
|
||||
void clear_released_list();
|
||||
void clear_pressed_list();
|
||||
void add_pressed_key(uint8_t row, uint8_t col);
|
||||
void add_released_key(uint8_t row, uint8_t col);
|
||||
void remove_pressed_key(uint8_t row, uint8_t col);
|
||||
void write(uint8_t register_address, uint8_t data);
|
||||
bool read(uint8_t register_address, uint8_t* data);
|
||||
|
||||
public:
|
||||
|
||||
struct PressedKey {
|
||||
uint8_t row;
|
||||
uint8_t col;
|
||||
uint32_t hold_time;
|
||||
};
|
||||
|
||||
struct ReleasedKey {
|
||||
uint8_t row;
|
||||
uint8_t col;
|
||||
};
|
||||
|
||||
std::string getName() const final { return "TCA8418"; }
|
||||
|
||||
std::string getDescription() const final { return "I2C-controlled keyboard scan IC"; }
|
||||
|
||||
explicit Tca8418(i2c_port_t port) : I2cDevice(port, TCA8418_ADDRESS) {
|
||||
delta_micros = 0;
|
||||
last_update_micros = 0;
|
||||
this_update_micros = 0;
|
||||
}
|
||||
|
||||
~Tca8418() {}
|
||||
|
||||
uint8_t num_rows;
|
||||
uint8_t num_cols;
|
||||
|
||||
uint32_t delta_micros;
|
||||
|
||||
std::array<PressedKey, KEY_EVENT_LIST_SIZE> pressed_list;
|
||||
std::array<ReleasedKey, KEY_EVENT_LIST_SIZE> released_list;
|
||||
uint8_t pressed_key_count;
|
||||
uint8_t released_key_count;
|
||||
|
||||
void init(uint8_t numrows, uint8_t numcols);
|
||||
bool update();
|
||||
uint8_t get_key_event();
|
||||
bool button_pressed(uint8_t row, uint8_t button_bit_position);
|
||||
bool button_released(uint8_t row, uint8_t button_bit_position);
|
||||
bool button_held(uint8_t row, uint8_t button_bit_position);
|
||||
};
|
||||
Reference in New Issue
Block a user