ThinkNode M9 (see M9_PORT.md audit section): restore nav-mode HW Back, Lua-app key forwarding + d-pad support, popup-registry gates widened, SD-backed data store + chat history, ADC2 battery sample filter, keyboard poll throttle + backlight, map tile cache to SD, spectrum sweep LR1110 fix, power-menu cleanup for buttonless hardware. Heltec V4 R8 (see R8_AUDIT.md): chat history/battery log follow SD adoption, reinsert watch no longer unmounts live store, tile cache prefers SD, CAP_ROTATABLE reboot trap defused, SD CS parked at boot. Shared: atomic threads-index write (tmp+rename), recursive scroll-into-view during keypad nav, textarea focus highlight. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
131 lines
5.0 KiB
C++
131 lines
5.0 KiB
C++
#include "HeltecV4Board.h"
|
|
|
|
void HeltecV4Board::begin() {
|
|
ESP32Board::begin();
|
|
|
|
|
|
#if defined(PIN_ADC_CTRL) && PIN_ADC_CTRL >= 0
|
|
pinMode(PIN_ADC_CTRL, OUTPUT);
|
|
digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive
|
|
#endif // V4-R8: no ADC-control MOSFET — the battery divider is read directly
|
|
|
|
loRaFEMControl.init();
|
|
|
|
#if defined(HELTEC_LORA_V4_R8) && defined(PIN_SD_CS)
|
|
// Park the shared-FSPI micro-SD's chip-select HIGH before LGFX starts
|
|
// driving the bus (display.begin + logo blit run before the first
|
|
// SD.begin): GPIO3 is a strapping pin with no firmware pull, and a card
|
|
// that samples CS low during that window can latch a confused state the
|
|
// mount ladder then has to rescue. Same discipline as M9Board/the pager.
|
|
pinMode(PIN_SD_CS, OUTPUT);
|
|
digitalWrite(PIN_SD_CS, HIGH);
|
|
// The R8 boot FEM-type line makes the auto-detect visible (the About page
|
|
// reports the board name only) — needed to verify GPIO46 really is the
|
|
// FEM TX-enable on this board rather than a plain LED (see r8 audit).
|
|
Serial.printf("[R8] FEM type=%d (0=GC1109 1=KCT8103L 2=other)\n", (int)loRaFEMControl.getFEMType());
|
|
#endif
|
|
|
|
periph_power.begin();
|
|
esp_reset_reason_t reason = esp_reset_reason();
|
|
if (reason == ESP_RST_DEEPSLEEP) {
|
|
long wakeup_source = esp_sleep_get_ext1_wakeup_status();
|
|
if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep)
|
|
startup_reason = BD_STARTUP_RX_PACKET;
|
|
}
|
|
|
|
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
|
|
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
|
|
#if defined(HELTEC_LORA_V4_R8)
|
|
// Release the digital-pad holds powerOffCb armed before deep sleep
|
|
// (VEXT / GPS_EN / backlight) — held pads would otherwise block this
|
|
// boot's own rail and backlight drives.
|
|
gpio_deep_sleep_hold_dis();
|
|
gpio_hold_dis((gpio_num_t)PIN_VEXT_EN);
|
|
gpio_hold_dis((gpio_num_t)PIN_GPS_EN);
|
|
gpio_hold_dis((gpio_num_t)PIN_TFT_LEDA_CTL);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void HeltecV4Board::onBeforeTransmit(void) {
|
|
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
|
loRaFEMControl.setTxModeEnable();
|
|
}
|
|
|
|
void HeltecV4Board::onAfterTransmit(void) {
|
|
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
|
loRaFEMControl.setRxModeEnable();
|
|
}
|
|
|
|
void HeltecV4Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {
|
|
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
|
|
|
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
|
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
|
|
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
|
|
|
|
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
|
|
|
|
loRaFEMControl.setRxModeEnableWhenMCUSleep();//It also needs to be enabled in receive mode
|
|
|
|
if (pin_wake_btn < 0) {
|
|
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
|
|
} else {
|
|
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
|
|
}
|
|
|
|
if (secs > 0) {
|
|
esp_sleep_enable_timer_wakeup(secs * 1000000);
|
|
}
|
|
|
|
// Finally set ESP32 into sleep
|
|
esp_deep_sleep_start(); // CPU halts here and never returns!
|
|
}
|
|
|
|
void HeltecV4Board::powerOff() {
|
|
enterDeepSleep(0);
|
|
}
|
|
|
|
uint16_t HeltecV4Board::getBattMilliVolts() {
|
|
#if defined(HELTEC_LORA_V4_R8)
|
|
// R8: match Meshtastic's heltec_v4_r8 variant — the always-connected
|
|
// high-resistance divider wants LOW attenuation (2.5 dB, full-scale
|
|
// ~1.05 V; the node sits at VBAT/5.07 ≈ 0.65-0.85 V) and a CALIBRATED
|
|
// millivolt read. The generic uncalibrated raw*(3.3/1024) at the default
|
|
// 11 dB under-read this divider by ~5-10%: a full 4.2 V pack displayed
|
|
// ~3.9 V, so the charging-bolt threshold (batteryFullMv()+50) was
|
|
// unreachable by construction and one ADC count quantized to ~16 mV of
|
|
// display (the "frozen at 3839 mV" report).
|
|
static bool s_atten_set = false;
|
|
if (!s_atten_set) { analogSetPinAttenuation(PIN_VBAT_READ, ADC_2_5db); s_atten_set = true; }
|
|
uint32_t mv = 0;
|
|
for (int i = 0; i < 8; i++) mv += analogReadMilliVolts(PIN_VBAT_READ);
|
|
return (uint16_t)((mv / 8) * adc_mult);
|
|
#else
|
|
analogReadResolution(10);
|
|
#if defined(PIN_ADC_CTRL) && PIN_ADC_CTRL >= 0
|
|
digitalWrite(PIN_ADC_CTRL, HIGH); // enable the battery divider
|
|
delay(10);
|
|
#endif
|
|
uint32_t raw = 0;
|
|
for (int i = 0; i < 8; i++) {
|
|
raw += analogRead(PIN_VBAT_READ);
|
|
}
|
|
raw = raw / 8;
|
|
#if defined(PIN_ADC_CTRL) && PIN_ADC_CTRL >= 0
|
|
digitalWrite(PIN_ADC_CTRL, LOW);
|
|
#endif
|
|
return (adc_mult * (3.3 / 1024.0) * raw) * 1000;
|
|
#endif
|
|
}
|
|
|
|
const char* HeltecV4Board::getManufacturerName() const {
|
|
#if defined(HELTEC_LORA_V4_R8)
|
|
return "Heltec V4-R8";
|
|
#elif defined(HELTEC_LORA_V4_TFT)
|
|
return loRaFEMControl.getFEMType() == KCT8103L_PA ? "Heltec V4.3 TFT" : "Heltec V4 TFT";
|
|
#else
|
|
return loRaFEMControl.getFEMType() == KCT8103L_PA ? "Heltec V4.3 OLED" : "Heltec V4 OLED";
|
|
#endif
|
|
}
|