From dc5bae54483e7b32fa01b25d894b023847918d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominic=20H=C3=B6glinger?= Date: Mon, 30 Jun 2025 21:45:25 +0200 Subject: [PATCH] Cleanup of initial Pager port --- .../Source/hal/TpagerKeyboard.cpp | 188 ++------- .../Source/hal/TpagerKeyboard.h | 7 +- Drivers/BQ27220/CMakeLists.txt | 5 + Drivers/BQ27220/README.md | 5 + Drivers/BQ27220/Source/Bq27220.cpp | 372 ++++++++++++++++++ Drivers/BQ27220/Source/Bq27220.h | 103 +++++ Drivers/TCA8418/Source/Tca8418.cpp | 9 +- Drivers/TCA8418/Source/Tca8418.h | 22 +- 8 files changed, 531 insertions(+), 180 deletions(-) create mode 100644 Drivers/BQ27220/CMakeLists.txt create mode 100644 Drivers/BQ27220/README.md create mode 100644 Drivers/BQ27220/Source/Bq27220.cpp create mode 100644 Drivers/BQ27220/Source/Bq27220.h diff --git a/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp b/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp index e0287d2e..462ae706 100644 --- a/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp +++ b/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp @@ -9,14 +9,14 @@ #define TAG "tpager_keyboard" -#define ENCODER_A GPIO_NUM_40 -#define ENCODER_B GPIO_NUM_41 -#define ENCODER_ENTER GPIO_NUM_7 -#define USER_BTN GPIO_NUM_0 +#define ENCODER_A GPIO_NUM_40 +#define ENCODER_B GPIO_NUM_41 +#define ENCODER_ENTER GPIO_NUM_7 -#define KB_ROWS 4 -#define KB_COLS 11 +#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'}, @@ -24,6 +24,7 @@ static constexpr char keymap_lc[KB_ROWS][KB_COLS] = { {'\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'}, @@ -31,7 +32,7 @@ static constexpr char keymap_uc[KB_ROWS][KB_COLS] = { {'\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'}, @@ -39,54 +40,28 @@ static constexpr char keymap_sy[KB_ROWS][KB_COLS] = { {'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'} }; -static QueueHandle_t rotaryMsg; static QueueHandle_t keyboardMsg; -/** - * The callback simulates press and release events, because the T-Deck - * keyboard only publishes press events on I2C. - * LVGL currently works without those extra release events, but they - * are implemented for correctness and future compatibility. - * - * @param indev_drv - * @param data - */ static void keyboard_read_callback(lv_indev_t* indev, lv_indev_data_t* data) { TpagerKeyboard* kb = (TpagerKeyboard*)lv_indev_get_user_data(indev); - uint8_t read_buffer = 0x00; static bool enter_prev = false; + char keypress = 0; // Defaults data->key = 0; data->state = LV_INDEV_STATE_RELEASED; - char encstate = 0; - char keypress = 0; if (xQueueReceive(keyboardMsg, &keypress, pdMS_TO_TICKS(50)) == pdPASS) { - TT_LOG_I(TAG, "KEY %c (%d)", keypress, keypress); data->key = keypress; data->state = LV_INDEV_STATE_PRESSED; - }/* else if (xQueueReceive(rotaryMsg, &encstate, pdMS_TO_TICKS(50)) == pdPASS) { - data->key = encstate; - if (encstate == LV_KEY_NEXT) data->enc_diff = 1; - if (encstate == LV_KEY_PREV) data->enc_diff = -1; - data->state = LV_INDEV_STATE_PRESSED; - }*/ - - //bool enter = !gpio_get_level(ENCODER_ENTER); - /*if (enter != enter_prev) { - TT_LOG_I(TAG, "Encoder CHANGE %d", enter); - data->key = LV_KEY_ENTER; - data->state = enter ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; - enter_prev = enter; - }*/ + } } static void encoder_read_callback(lv_indev_t* indev, lv_indev_data_t* data) { TpagerKeyboard* kb = (TpagerKeyboard*)lv_indev_get_user_data(indev); - uint8_t read_buffer = 0x00; - const int enterFilterThreshold = 2; - static int enterFilter = 0; + const int enter_filter_threshold = 2; + static int enter_filter = 0; + const int pulses_click = 4; static int pulses_prev = 0; // Defaults @@ -95,53 +70,22 @@ static void encoder_read_callback(lv_indev_t* indev, lv_indev_data_t* data) { int pulses = kb->getEncoderPulses(); int pulse_diff = (pulses - pulses_prev); - if ((pulse_diff > 4) || (pulse_diff < -4)) { - data->enc_diff = pulse_diff / 4; + if ((pulse_diff > pulses_click) || (pulse_diff < -pulses_click)) { + data->enc_diff = pulse_diff / pulses_click; pulses_prev = pulses; } - // TT_LOG_I(TAG, "Encoder DIFF %d PULSE %d", data->enc_diff, pulses); - - char encstate = 0; - char keypress = 0; - /*if (xQueueReceive(rotaryMsg, &encstate, pdMS_TO_TICKS(50)) == pdPASS) { - //data->key = encstate; - if (encstate == LV_KEY_NEXT) data->enc_diff = 1; - if (encstate == LV_KEY_PREV) data->enc_diff = -1; - if (encstate == LV_KEY_RIGHT) data->enc_diff = 100; - if (encstate == LV_KEY_LEFT) data->enc_diff = -100; - //data->state = LV_INDEV_STATE_PRESSED; - }*/ - bool enter = !gpio_get_level(ENCODER_ENTER); - if (enter && (enterFilter < enterFilterThreshold)) { - enterFilter++; + if (enter && (enter_filter < enter_filter_threshold)) { + enter_filter++; } - if (!enter && (enterFilter > 0)) - { - enterFilter--; + if (!enter && (enter_filter > 0)) { + enter_filter--; } - if (enterFilter == enterFilterThreshold) { + if (enter_filter == enter_filter_threshold) { data->state = LV_INDEV_STATE_PRESSED; } - //TT_LOG_I(TAG, "ENTER F %d", enterFilter); -} - -static void button_read_callback(lv_indev_t* indev, lv_indev_data_t* data) { - if(!gpio_get_level(USER_BTN)) { /*Is there a button press? (E.g. -1 indicated no button was pressed)*/ - data->state = LV_INDEV_STATE_PRESSED; /*Set the pressed state*/ - TT_LOG_I(TAG, "USR BTN PRESS"); - - } else { - data->state = LV_INDEV_STATE_RELEASED; /*Set the released state*/ - } - - data->btn_id = 0; /*Save the last button*/ -} - -void TpagerKeyboard::processEncoder() { - } void TpagerKeyboard::processKeyboard() { @@ -151,28 +95,22 @@ void TpagerKeyboard::processKeyboard() { static bool cap_toggle_armed = true; if (keypad->update()) { - for (int i=0; ipressed_key_count; i++) { + 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; - TT_LOG_I(TAG, "KB PRESS R=%d C=%d", row, col); - if ((row == 1) && (col == 10)) { sym_pressed = true; - TT_LOG_I(TAG, "KB SYM ON"); } if ((row == 2) && (col == 7)) { shift_pressed = true; - TT_LOG_I(TAG, "KB SHFT ON"); } } - if ((sym_pressed && shift_pressed) && cap_toggle_armed) { cap_toggle = !cap_toggle; cap_toggle_armed = false; - TT_LOG_I(TAG, "KB CAP TOGGLE"); } for (int i=0; ipressed_key_count; i++) { @@ -195,53 +133,27 @@ void TpagerKeyboard::processKeyboard() { auto row = keypad->released_list[i].row; auto col = keypad->released_list[i].col; - TT_LOG_I(TAG, "KB REL R=%d C=%d", row, col); - if ((row == 1) && (col == 10)) { sym_pressed = false; - TT_LOG_I(TAG, "KB SYM OFF"); } if ((row == 2) && (col == 7)) { shift_pressed = false; - TT_LOG_I(TAG, "KB SHFT OFF"); } } if ((!sym_pressed && !shift_pressed) && !cap_toggle_armed) { cap_toggle_armed = true; - TT_LOG_I(TAG, "KB CAP TOGGLE REARM"); } } - /* - char encstate = encoder.process(); - char encbtn = 0; - switch (encstate) { - case DIR_CW: - TT_LOG_I(TAG, "ENC CW"); - encbtn = sym_pressed ? LV_KEY_RIGHT : LV_KEY_NEXT; - break; - case DIR_CCW: - TT_LOG_I(TAG, "ENC CCW"); - encbtn = sym_pressed ? LV_KEY_LEFT : LV_KEY_PREV; - break; - default: - break; - } - - if (encbtn != 0) xQueueSend(rotaryMsg, (void *)&encbtn, portMAX_DELAY); - */ } bool TpagerKeyboard::start(lv_display_t* display) { - //encoder.begin(); initEncoder(); keypad->init(KB_ROWS, KB_COLS); gpio_input_enable(ENCODER_ENTER); - gpio_input_enable(USER_BTN); - assert(encoderTimer == nullptr); - encoderTimer = std::make_unique(tt::Timer::Type::Periodic, [this] { - processEncoder(); + assert(inputTimer == nullptr); + inputTimer = std::make_unique(tt::Timer::Type::Periodic, [this] { processKeyboard(); }); @@ -251,40 +163,27 @@ bool TpagerKeyboard::start(lv_display_t* display) { lv_indev_set_display(kbHandle, display); lv_indev_set_user_data(kbHandle, this); - btnHandle = lv_indev_create(); - lv_indev_set_type(btnHandle, LV_INDEV_TYPE_BUTTON); - static const lv_point_t btn_points[1] = { - {472, 111} - //{20, 30} - }; - lv_indev_set_button_points(btnHandle, btn_points); - lv_indev_set_read_cb(btnHandle, &button_read_callback); - lv_indev_set_display(btnHandle, display); - lv_indev_set_user_data(btnHandle, 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); - encoderTimer->start(20 / portTICK_PERIOD_MS); + inputTimer->start(20 / portTICK_PERIOD_MS); return true; } bool TpagerKeyboard::stop() { - assert(encoderTimer); + assert(inputTimer); - encoderTimer->stop(); - encoderTimer = nullptr; + inputTimer->stop(); + inputTimer = nullptr; lv_indev_delete(kbHandle); kbHandle = nullptr; lv_indev_delete(encHandle); encHandle = nullptr; - lv_indev_delete(btnHandle); - btnHandle = nullptr; return true; } @@ -293,21 +192,24 @@ bool TpagerKeyboard::isAttached() const { } 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 = -127, - .high_limit = 126, + .low_limit = low_limit, + .high_limit = high_limit, .flags = { .accum_count = 1 }, }; ESP_ERROR_CHECK(pcnt_new_unit(&unit_config, &encPcntUnit)); - //ESP_LOGI(encoder, "set glitch filter"); pcnt_glitch_filter_config_t filter_config = { .max_glitch_ns = 1000, }; ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(encPcntUnit, &filter_config)); - //ESP_LOGI(encoder, "install pcnt channels"); pcnt_chan_config_t chan_a_config = { .edge_gpio_num = ENCODER_A, .level_gpio_num = ENCODER_B, @@ -321,34 +223,17 @@ void TpagerKeyboard::initEncoder(void) { pcnt_channel_handle_t pcnt_chan_b = NULL; ESP_ERROR_CHECK(pcnt_new_channel(encPcntUnit, &chan_b_config, &pcnt_chan_b)); - //ESP_LOGI(encoder, "set edge and level actions for pcnt channels"); 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_LOGI(encoder, "add watch points and register callbacks"); + ESP_ERROR_CHECK(pcnt_unit_add_watch_point(encPcntUnit, low_limit)); + ESP_ERROR_CHECK(pcnt_unit_add_watch_point(encPcntUnit, high_limit)); - - int watch_points[] = {-127,126}; - for (size_t i = 0; i < sizeof(watch_points) / sizeof(watch_points[0]); i++) { - ESP_ERROR_CHECK(pcnt_unit_add_watch_point(encPcntUnit, watch_points[i])); - } - /*pcnt_event_callbacks_t cbs = { - .on_reach = pcnt_on_reach, - }; - hEncoder.queue = xQueueCreate(10, sizeof(int)); - ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(encPcntUnit, &cbs, hEncoder.queue)); - */ - //ESP_LOGI(encoder, "enable pcnt unit"); ESP_ERROR_CHECK(pcnt_unit_enable(encPcntUnit)); - //ESP_LOGI(encoder, "clear pcnt unit"); ESP_ERROR_CHECK(pcnt_unit_clear_count(encPcntUnit)); - //ESP_LOGI(encoder, "start pcnt unit"); ESP_ERROR_CHECK(pcnt_unit_start(encPcntUnit)); - - //External callback register. - //hEncoder.callback = callback; } int TpagerKeyboard::getEncoderPulses() { @@ -360,7 +245,6 @@ int TpagerKeyboard::getEncoderPulses() { extern std::shared_ptr tca8418; std::shared_ptr createKeyboard() { - rotaryMsg = xQueueCreate(5, sizeof(char)); keyboardMsg = xQueueCreate(20, sizeof(char)); return std::make_shared(tca8418); diff --git a/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.h b/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.h index b5fabe8a..dfeaa949 100644 --- a/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.h +++ b/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.h @@ -14,13 +14,12 @@ class TpagerKeyboard : public tt::hal::keyboard::KeyboardDevice { private: lv_indev_t* _Nullable kbHandle = nullptr; lv_indev_t* _Nullable encHandle = nullptr; - lv_indev_t* _Nullable btnHandle = nullptr; - //Rotary encoder; pcnt_unit_handle_t encPcntUnit = nullptr; std::shared_ptr keypad; - std::unique_ptr encoderTimer; + std::unique_ptr inputTimer; void initEncoder(void); + void processKeyboard(); public: TpagerKeyboard(std::shared_ptr tca) : keypad(std::move(tca)) {} @@ -34,8 +33,6 @@ public: bool isAttached() const override; lv_indev_t* _Nullable getLvglIndev() override { return kbHandle; } - void processEncoder(); - void processKeyboard(); int getEncoderPulses(); }; diff --git a/Drivers/BQ27220/CMakeLists.txt b/Drivers/BQ27220/CMakeLists.txt new file mode 100644 index 00000000..8074f3b3 --- /dev/null +++ b/Drivers/BQ27220/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "Source" + INCLUDE_DIRS "Source" + REQUIRES Tactility +) diff --git a/Drivers/BQ27220/README.md b/Drivers/BQ27220/README.md new file mode 100644 index 00000000..5ecc1181 --- /dev/null +++ b/Drivers/BQ27220/README.md @@ -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) diff --git a/Drivers/BQ27220/Source/Bq27220.cpp b/Drivers/BQ27220/Source/Bq27220.cpp new file mode 100644 index 00000000..9a67ff51 --- /dev/null +++ b/Drivers/BQ27220/Source/Bq27220.cpp @@ -0,0 +1,372 @@ +#include "Bq27220.h" +#include + +#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(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; +} diff --git a/Drivers/BQ27220/Source/Bq27220.h b/Drivers/BQ27220/Source/Bq27220.h new file mode 100644 index 00000000..abf5b86d --- /dev/null +++ b/Drivers/BQ27220/Source/Bq27220.h @@ -0,0 +1,103 @@ +#pragma once + +#include + +#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 + 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); +}; diff --git a/Drivers/TCA8418/Source/Tca8418.cpp b/Drivers/TCA8418/Source/Tca8418.cpp index 0acf98f3..9fbfb4b8 100644 --- a/Drivers/TCA8418/Source/Tca8418.cpp +++ b/Drivers/TCA8418/Source/Tca8418.cpp @@ -93,8 +93,6 @@ void Tca8418::init(uint8_t numrows, uint8_t numcols) { clear_released_list(); clear_pressed_list(); - - //delayMicroseconds(100); } bool Tca8418::update() { @@ -105,10 +103,10 @@ bool Tca8418::update() { // TODO: read gpio R7/R6 status? 0x14 bits 7&6 // read(0x14, &new_keycode) - this_update_micros = 0; //micros(); + // TODO: use tick function to get an update delta time + this_update_micros = 0; delta_micros = this_update_micros - last_update_micros; - // if there is a new event if (key_event > 0) { key_code = key_event & 0x7F; key_down = (key_event & 0x80) >> 7; @@ -131,7 +129,7 @@ bool Tca8418::update() { return true; } - // increment hold times for pressed keys + // Increment hold times for pressed keys for (int i=0; i + #include #define TCA8418_ADDRESS 0x34U @@ -38,35 +40,21 @@ public: 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; - - pressed_list = new PressedKey[KEY_EVENT_LIST_SIZE]; - released_list = new ReleasedKey[KEY_EVENT_LIST_SIZE]; - matrix_state = new uint16_t[num_rows]; - matrix_state_prev = new uint16_t[num_rows]; } - ~Tca8418() { - delete [] matrix_state_prev; - delete [] matrix_state; - delete [] pressed_list; - delete [] released_list; - } + ~Tca8418() {} uint8_t num_rows; uint8_t num_cols; - uint16_t *matrix_state; - uint16_t *matrix_state_prev; - uint32_t delta_micros; - PressedKey *pressed_list; - ReleasedKey *released_list; + std::array pressed_list; + std::array released_list; uint8_t pressed_key_count; uint8_t released_key_count;