mirror of
https://github.com/ddv2005/AirsoftTracker.git
synced 2026-01-09 12:36:54 +00:00
434 lines
14 KiB
C++
434 lines
14 KiB
C++
#include "power.h"
|
|
#include "utils.h"
|
|
|
|
#if defined(HAS_AXP20X) || defined(USE_XPOWERSLIB)
|
|
bool pmu_irq = false;
|
|
#endif
|
|
bool Power::setup()
|
|
{
|
|
#ifdef HAS_AXP20X
|
|
axp192Init();
|
|
#endif
|
|
|
|
#ifdef USE_XPOWERSLIB
|
|
#ifdef PMU_USE_WIRE1
|
|
TwoWire &wire = Wire1;
|
|
#else
|
|
TwoWire &wire = Wire;
|
|
#endif
|
|
m_power = new XPowersPMU(wire);
|
|
m_pmuFound = m_power->init();
|
|
if(m_pmuFound)
|
|
{
|
|
DEBUG_MSG("PMU Found\n");
|
|
m_power->setChargingLedMode(XPOWERS_CHG_LED_CTRL_CHG);
|
|
|
|
/**
|
|
* gnss module power channel
|
|
* The default ALDO4 is off, you need to turn on the GNSS power first, otherwise it will be invalid during
|
|
* initialization
|
|
*/
|
|
m_power->setPowerChannelVoltage(XPOWERS_ALDO4, 3300);
|
|
m_power->enablePowerOutput(XPOWERS_ALDO4);
|
|
|
|
// lora radio power channel
|
|
m_power->setPowerChannelVoltage(XPOWERS_ALDO3, 3300);
|
|
m_power->enablePowerOutput(XPOWERS_ALDO3);
|
|
|
|
// m.2 interface
|
|
m_power->setPowerChannelVoltage(XPOWERS_DCDC3, 3300);
|
|
m_power->enablePowerOutput(XPOWERS_DCDC3);
|
|
|
|
/**
|
|
* ALDO2 cannot be turned off.
|
|
* It is a necessary condition for sensor communication.
|
|
* It must be turned on to properly access the sensor and screen
|
|
* It is also responsible for the power supply of PCF8563
|
|
*/
|
|
m_power->setPowerChannelVoltage(XPOWERS_ALDO2, 3300);
|
|
m_power->enablePowerOutput(XPOWERS_ALDO2);
|
|
|
|
// 6-axis , magnetometer ,bme280 , oled screen power channel
|
|
m_power->setPowerChannelVoltage(XPOWERS_ALDO1, 3300);
|
|
m_power->enablePowerOutput(XPOWERS_ALDO1);
|
|
|
|
// sdcard power channel
|
|
m_power->setPowerChannelVoltage(XPOWERS_BLDO1, 3300);
|
|
m_power->enablePowerOutput(XPOWERS_BLDO1);
|
|
|
|
// not use channel
|
|
m_power->disablePowerOutput(XPOWERS_DCDC2); // not elicited
|
|
m_power->disablePowerOutput(XPOWERS_DCDC5); // not elicited
|
|
m_power->disablePowerOutput(XPOWERS_DLDO1); // Invalid power channel, it does not exist
|
|
m_power->disablePowerOutput(XPOWERS_DLDO2); // Invalid power channel, it does not exist
|
|
m_power->disablePowerOutput(XPOWERS_VBACKUP);
|
|
|
|
m_power->setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2);
|
|
m_power->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_1000MA);
|
|
|
|
|
|
m_power->disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
|
m_power->clearIrqStatus();
|
|
|
|
// TBeam1.1 /T-Beam S3-Core has no external TS detection,
|
|
// it needs to be disabled, otherwise it will cause abnormal charging
|
|
m_power->disableTSPinMeasure();
|
|
|
|
// PMU->enableSystemVoltageMeasure();
|
|
m_power->enableVbusVoltageMeasure();
|
|
m_power->enableBattVoltageMeasure();
|
|
m_power->setPowerKeyPressOnTime(XPOWERS_POWERON_1S);
|
|
m_power->setPowerKeyPressOffTime(XPOWERS_POWEROFF_6S);
|
|
}
|
|
else
|
|
{
|
|
DEBUG_MSG("PMU NOT Found\n");
|
|
}
|
|
#ifdef PMU_IRQ
|
|
uint64_t pmuIrqMask = 0;
|
|
|
|
if (m_power->getChipModel() == XPOWERS_AXP192) {
|
|
pmuIrqMask = XPOWERS_AXP192_VBUS_INSERT_IRQ | XPOWERS_AXP192_BAT_INSERT_IRQ | XPOWERS_AXP192_PKEY_SHORT_IRQ | XPOWERS_AXP192_PKEY_LONG_IRQ;
|
|
} else if (m_power->getChipModel() == XPOWERS_AXP2101) {
|
|
pmuIrqMask = XPOWERS_AXP2101_VBUS_INSERT_IRQ | XPOWERS_AXP2101_BAT_INSERT_IRQ | XPOWERS_AXP2101_PKEY_SHORT_IRQ | XPOWERS_AXP2101_PKEY_LONG_IRQ;
|
|
}
|
|
|
|
pinMode(PMU_IRQ, INPUT);
|
|
attachInterrupt(
|
|
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
|
|
|
// we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ because it occurs repeatedly while there is
|
|
// no battery also it could cause inadvertent waking from light sleep just because the battery filled
|
|
// we don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while no battery installed
|
|
// we don't look at AXPXXX_VBUS_REMOVED_IRQ because we don't have anything hooked to vbus
|
|
m_power->enableIRQ(pmuIrqMask);
|
|
|
|
m_power->clearIrqStatus();
|
|
#endif
|
|
#endif
|
|
concurrency::PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
|
|
setPeriod(1);
|
|
|
|
return true;
|
|
}
|
|
#ifdef BATTERY_PIN
|
|
static float read_battery() {
|
|
#ifdef ADC_CTRL
|
|
digitalWrite(ADC_CTRL, HIGH);
|
|
delay(2);
|
|
#endif
|
|
uint16_t v = analogRead(BATTERY_PIN);
|
|
//float battery_voltage = (float)v/4095*2*3.3*1.1;
|
|
float battery_voltage = (float)v*BATTERY_M;
|
|
#ifdef ADC_CTRL
|
|
digitalWrite(ADC_CTRL, LOW);
|
|
#endif
|
|
return battery_voltage;
|
|
}
|
|
#else
|
|
static float read_battery() {
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/// Reads power status to powerStatus singleton.
|
|
//
|
|
// TODO(girts): move this and other axp stuff to power.h/power.cpp.
|
|
void Power::readPowerStatus()
|
|
{
|
|
#ifdef HAS_AXP20X
|
|
bool hasBattery = axp.isBatteryConnect();
|
|
int batteryVoltageMv = 0;
|
|
uint8_t batteryChargePercent = 0;
|
|
if (hasBattery)
|
|
{
|
|
batteryVoltageMv = axp.getBattVoltage();
|
|
// If the AXP192 returns a valid battery percentage, use it
|
|
if (axp.getBattPercentage() >= 0)
|
|
{
|
|
batteryChargePercent = axp.getBattPercentage();
|
|
}
|
|
else
|
|
{
|
|
// If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error
|
|
// In that case, we compute an estimate of the charge percent based on maximum and minimum voltages defined in power.h
|
|
batteryChargePercent = clamp((int)(((batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)), 0, 100);
|
|
}
|
|
}
|
|
|
|
// Notify any status instances that are observing us
|
|
const PowerStatus powerStatus = PowerStatus(hasBattery, axp.isVBUSPlug(), axp.isChargeing(), batteryVoltageMv, batteryChargePercent);
|
|
newStatus.notifyObservers(&powerStatus);
|
|
|
|
// If we have a battery at all and it is less than 10% full, force deep sleep
|
|
if (powerStatus.getHasBattery() && !powerStatus.getHasUSB() &&
|
|
axp.getBattVoltage() < MIN_BAT_MILLIVOLTS)
|
|
{
|
|
//powerFSM.trigger(EVENT_LOW_BATTERY);
|
|
}
|
|
#elif defined(USE_XPOWERSLIB)
|
|
bool hasBattery = m_power->isBatteryConnect();
|
|
int batteryVoltageMv = 0;
|
|
uint8_t batteryChargePercent = 0;
|
|
if (hasBattery)
|
|
{
|
|
batteryVoltageMv = m_power->getBattVoltage();
|
|
// If the AXP192 returns a valid battery percentage, use it
|
|
if (m_power->getBatteryPercent() >= 0)
|
|
{
|
|
batteryChargePercent = m_power->getBatteryPercent();
|
|
}
|
|
else
|
|
{
|
|
// If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error
|
|
// In that case, we compute an estimate of the charge percent based on maximum and minimum voltages defined in power.h
|
|
batteryChargePercent = clamp((int)(((batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)), 0, 100);
|
|
}
|
|
}
|
|
|
|
// Notify any status instances that are observing us
|
|
const PowerStatus powerStatus = PowerStatus(hasBattery, m_power->isVbusIn(), m_power->isCharging(), batteryVoltageMv, batteryChargePercent);
|
|
newStatus.notifyObservers(&powerStatus);
|
|
|
|
// If we have a battery at all and it is less than 10% full, force deep sleep
|
|
if (powerStatus.getHasBattery() && !powerStatus.getHasUSB() &&
|
|
m_power->getBattVoltage() < MIN_BAT_MILLIVOLTS)
|
|
{
|
|
//powerFSM.trigger(EVENT_LOW_BATTERY);
|
|
}
|
|
#else
|
|
int batteryVoltageMv = read_battery()*1000;
|
|
uint8_t batteryChargePercent = clamp((int)(((batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)), 0, 100);
|
|
const PowerStatus powerStatus = PowerStatus(true, false, batteryVoltageMv>4200, batteryVoltageMv, batteryChargePercent);
|
|
newStatus.notifyObservers(&powerStatus);
|
|
#endif
|
|
}
|
|
|
|
void Power::doTask()
|
|
{
|
|
readPowerStatus();
|
|
|
|
// Only read once every 20 seconds once the power status for the app has been initialized
|
|
if (statusHandler && statusHandler->isInitialized())
|
|
setPeriod(1000 * 20);
|
|
}
|
|
|
|
void Power::gpsOff()
|
|
{
|
|
#ifdef USE_XPOWERSLIB
|
|
m_power->disablePowerOutput(XPOWERS_ALDO4);
|
|
#endif
|
|
|
|
#ifdef HAS_AXP20X
|
|
axp.setPowerOutPut(AXP192_LDO3, AXP202_OFF);
|
|
#endif
|
|
}
|
|
|
|
void Power::shutdown()
|
|
{
|
|
#ifdef USE_XPOWERSLIB
|
|
m_power->shutdown();
|
|
#endif
|
|
#ifdef HAS_AXP20X
|
|
axp.shutdown();
|
|
#endif
|
|
}
|
|
|
|
void Power::gpsOn()
|
|
{
|
|
#ifdef USE_XPOWERSLIB
|
|
m_power->enablePowerOutput(XPOWERS_ALDO4);
|
|
#endif
|
|
#ifdef HAS_AXP20X
|
|
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON);
|
|
#endif
|
|
}
|
|
|
|
#ifdef AXP192_SLAVE_ADDRESS
|
|
/**
|
|
* Init the power manager chip
|
|
*
|
|
* axp192 power
|
|
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192
|
|
share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) LDO1
|
|
30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can
|
|
not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
|
|
*/
|
|
|
|
#ifdef HAS_AXP20X
|
|
void Power::axp192Init()
|
|
{
|
|
if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS))
|
|
{
|
|
DEBUG_MSG("AXP192 Begin PASS\n");
|
|
|
|
// axp.setChgLEDMode(LED_BLINK_4HZ);
|
|
axp.setChgLEDModeCharging();
|
|
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
|
|
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
|
|
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
|
|
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
|
|
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
|
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
|
DEBUG_MSG("----------------------------------------\n");
|
|
axp.setDCDC1Voltage(3300); // for the OLED power
|
|
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio
|
|
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power
|
|
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
|
|
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
|
|
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
|
|
axp.setDCDC1Voltage(3300); // for the OLED power
|
|
|
|
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
|
|
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
|
|
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
|
|
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
|
|
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
|
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
|
|
|
axp.setChargingTargetVoltage(AXP202_TARGET_VOL_4_2V);
|
|
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA); // actual limit (in HW) on the tbeam is 450mA
|
|
axp.setStartupTime(2);
|
|
axp.setShutdownTime(0);
|
|
axp.EnableCoulombcounter();
|
|
#if 0
|
|
|
|
// Not connected
|
|
//val = 0xfc;
|
|
//axp._writeByte(AXP202_VHTF_CHGSET, 1, &val); // Set temperature protection
|
|
|
|
//not used
|
|
//val = 0x46;
|
|
//axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection
|
|
#endif
|
|
axp.debugCharging();
|
|
|
|
#ifdef PMU_IRQ
|
|
pinMode(PMU_IRQ, INPUT);
|
|
attachInterrupt(
|
|
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
|
|
|
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
|
|
axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ |
|
|
AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ | AXP202_PEK_LONGPRESS_IRQ,
|
|
1);
|
|
|
|
axp.clearIRQ();
|
|
#endif
|
|
readPowerStatus();
|
|
}
|
|
else
|
|
{
|
|
DEBUG_MSG("AXP192 Begin FAIL\n");
|
|
delay(1000);
|
|
abort();
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
int Power::loop()
|
|
{
|
|
int result = 0;
|
|
#ifdef HAS_AXP20X
|
|
#ifdef PMU_IRQ
|
|
if (pmu_irq)
|
|
{
|
|
pmu_irq = false;
|
|
axp.readIRQ();
|
|
|
|
DEBUG_MSG("pmu irq!\n");
|
|
|
|
if (axp.isChargingIRQ())
|
|
{
|
|
DEBUG_MSG("Battery start charging\n");
|
|
}
|
|
if (axp.isChargingDoneIRQ())
|
|
{
|
|
DEBUG_MSG("Battery fully charged\n");
|
|
}
|
|
if (axp.isVbusRemoveIRQ())
|
|
{
|
|
DEBUG_MSG("USB unplugged\n");
|
|
}
|
|
if (axp.isVbusPlugInIRQ())
|
|
{
|
|
DEBUG_MSG("USB plugged In\n");
|
|
}
|
|
if (axp.isBattPlugInIRQ())
|
|
{
|
|
DEBUG_MSG("Battery inserted\n");
|
|
}
|
|
if (axp.isBattRemoveIRQ())
|
|
{
|
|
DEBUG_MSG("Battery removed\n");
|
|
}
|
|
if (axp.isPEKShortPressIRQ())
|
|
{
|
|
DEBUG_MSG("PEK short button press\n");
|
|
result |= 1;
|
|
}
|
|
if (axp.isPEKLongtPressIRQ())
|
|
{
|
|
DEBUG_MSG("PEK long button press\n");
|
|
result |= 2;
|
|
}
|
|
|
|
readPowerStatus();
|
|
axp.clearIRQ();
|
|
}
|
|
#endif
|
|
#else
|
|
#ifdef USE_XPOWERSLIB
|
|
#ifdef PMU_IRQ
|
|
if (pmu_irq)
|
|
{
|
|
pmu_irq = false;
|
|
m_power->getIrqStatus();
|
|
|
|
DEBUG_MSG("pmu irq!\n");
|
|
|
|
if (m_power->isBatChargeStartIrq())
|
|
{
|
|
DEBUG_MSG("Battery start charging\n");
|
|
}
|
|
if (m_power->isBatChargeDoneIrq())
|
|
{
|
|
DEBUG_MSG("Battery fully charged\n");
|
|
}
|
|
if (m_power->isVbusRemoveIrq())
|
|
{
|
|
DEBUG_MSG("USB unplugged\n");
|
|
}
|
|
if (m_power->isBatInsertIrq())
|
|
{
|
|
DEBUG_MSG("USB plugged In\n");
|
|
}
|
|
if (m_power->isBatInsertIrq())
|
|
{
|
|
DEBUG_MSG("Battery inserted\n");
|
|
}
|
|
if (m_power->isBatRemoveIrq())
|
|
{
|
|
DEBUG_MSG("Battery removed\n");
|
|
}
|
|
if (m_power->isPekeyShortPressIrq())
|
|
{
|
|
DEBUG_MSG("PEK short button press\n");
|
|
result |= 1;
|
|
}
|
|
if (m_power->isPekeyLongPressIrq())
|
|
{
|
|
DEBUG_MSG("PEK long button press\n");
|
|
result |= 2;
|
|
}
|
|
|
|
readPowerStatus();
|
|
m_power->clearIrqStatus();
|
|
}
|
|
#endif
|
|
#endif
|
|
#endif
|
|
return result;
|
|
}
|