|
35 | 35 | #include "nrfx_power.h" |
36 | 36 | #endif |
37 | 37 |
|
| 38 | +#if defined(ARCH_STM32WL) && BATTERY_PIN == AVBAT |
| 39 | +#include "stm32yyxx_ll_adc.h" |
| 40 | + |
| 41 | +/* Analog read resolution */ |
| 42 | +#if defined(LL_ADC_RESOLUTION_12B) |
| 43 | +#define LL_ADC_RESOLUTION LL_ADC_RESOLUTION_12B |
| 44 | +#define BATTERY_SENSE_RESOLUTION_BITS 12 |
| 45 | +#elif defined(LL_ADC_DS_DATA_WIDTH_12_BIT) |
| 46 | +#define LL_ADC_RESOLUTION LL_ADC_DS_DATA_WIDTH_12_BIT |
| 47 | +#else |
| 48 | +#error "ADC resolution could not be defined!" |
| 49 | +#endif |
| 50 | +#define ADC_RANGE 4096 |
| 51 | +#endif |
| 52 | + |
38 | 53 | #if defined(DEBUG_HEAP_MQTT) && !MESHTASTIC_EXCLUDE_MQTT |
39 | 54 | #include "mqtt/MQTT.h" |
40 | 55 | #include "target_specific.h" |
@@ -698,6 +713,8 @@ bool Power::setup() |
698 | 713 | found = true; |
699 | 714 | } else if (meshSolarInit()) { |
700 | 715 | found = true; |
| 716 | + } else if (stm32wlInit()) { |
| 717 | + found = true; |
701 | 718 | } else if (analogInit()) { |
702 | 719 | found = true; |
703 | 720 | } |
@@ -1602,6 +1619,110 @@ bool Power::meshSolarInit() |
1602 | 1619 | } |
1603 | 1620 | #endif |
1604 | 1621 |
|
| 1622 | +#if defined(ARCH_STM32WL) && BATTERY_PIN == AVBAT |
| 1623 | + |
| 1624 | +/** |
| 1625 | + * STM32WL internal VBAT ADC channel |
| 1626 | + */ |
| 1627 | + |
| 1628 | +// 3300mV is a placeholder value in case of future errata e.g. STM32U0 |
| 1629 | +uint32_t Vref = 3300; |
| 1630 | + |
| 1631 | +class Stm32wlBatteryLevel : public HasBatteryLevel |
| 1632 | +{ |
| 1633 | + |
| 1634 | + public: |
| 1635 | + /** |
| 1636 | + * Battery state of charge, from 0 to 100 or -1 for unknown |
| 1637 | + * Copied wholesale from AnalogBatteryLevel |
| 1638 | + */ |
| 1639 | + virtual int getBatteryPercent() override |
| 1640 | + { |
| 1641 | + float v = getBattVoltage(); |
| 1642 | + |
| 1643 | + if (v < noBatVolt) |
| 1644 | + return -1; // If voltage is super low assume no battery installed |
| 1645 | + |
| 1646 | + float battery_SOC = 0.0; |
| 1647 | + uint16_t voltage = v / NUM_CELLS; // single cell voltage (average) |
| 1648 | + for (int i = 0; i < NUM_OCV_POINTS; i++) { |
| 1649 | + if (OCV[i] <= voltage) { |
| 1650 | + if (i == 0) { |
| 1651 | + battery_SOC = 100.0; // 100% full |
| 1652 | + } else { |
| 1653 | + // interpolate between OCV[i] and OCV[i-1] |
| 1654 | + battery_SOC = (float)100.0 / (NUM_OCV_POINTS - 1.0) * |
| 1655 | + (NUM_OCV_POINTS - 1.0 - i + ((float)voltage - OCV[i]) / (OCV[i - 1] - OCV[i])); |
| 1656 | + } |
| 1657 | + break; |
| 1658 | + } |
| 1659 | + } |
| 1660 | +#if defined(BATTERY_CHARGING_INV) |
| 1661 | + // bit of trickery to show 99% up until the charge finishes |
| 1662 | + if (!digitalRead(BATTERY_CHARGING_INV) && battery_SOC > 99) |
| 1663 | + battery_SOC = 99; |
| 1664 | +#endif |
| 1665 | + return clamp((int)(battery_SOC), 0, 100); |
| 1666 | + } |
| 1667 | + |
| 1668 | + /** |
| 1669 | + * Read VREF in mV once, used to scale all subsequent VBAT readings |
| 1670 | + */ |
| 1671 | + bool runOnce() |
| 1672 | + { |
| 1673 | +#ifdef __LL_ADC_CALC_VREFANALOG_VOLTAGE |
| 1674 | + Vref = __LL_ADC_CALC_VREFANALOG_VOLTAGE(analogRead(AVREF), LL_ADC_RESOLUTION); |
| 1675 | +#else |
| 1676 | + Vref = VREFINT * ADC_RANGE / analogRead(AVREF); // ADC sample to mV |
| 1677 | +#endif |
| 1678 | + return true; |
| 1679 | + } |
| 1680 | + |
| 1681 | + /** |
| 1682 | + * Read VBAT in mV |
| 1683 | + */ |
| 1684 | + virtual uint16_t getBattVoltage() override |
| 1685 | + { |
| 1686 | + // VBAT pin is internally connected to a bridge divider by three (DS13105§3.18.3) |
| 1687 | + return 3 * __LL_ADC_CALC_DATA_TO_VOLTAGE(Vref, analogRead(BATTERY_PIN), LL_ADC_RESOLUTION); |
| 1688 | + } |
| 1689 | + |
| 1690 | + /** |
| 1691 | + * If BATTERY_PIN = AVBAT, assume a battery is present (otherwise don't define it!) |
| 1692 | + */ |
| 1693 | + virtual bool isBatteryConnect() override { return true; } |
| 1694 | + |
| 1695 | + private: |
| 1696 | + const uint16_t OCV[NUM_OCV_POINTS] = {OCV_ARRAY}; |
| 1697 | + const float chargingVolt = (OCV[0] + 10) * NUM_CELLS; |
| 1698 | + const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS; |
| 1699 | +}; |
| 1700 | + |
| 1701 | +Stm32wlBatteryLevel stm32wlLevel; |
| 1702 | + |
| 1703 | +/** |
| 1704 | + * Init the STM32WL internal VBAT ADC channel |
| 1705 | + */ |
| 1706 | +bool Power::stm32wlInit() |
| 1707 | +{ |
| 1708 | + bool result = stm32wlLevel.runOnce(); |
| 1709 | + LOG_DEBUG("Power::stm32wlInit is %s", result ? "ready" : "not ready yet"); |
| 1710 | + if (!result) |
| 1711 | + return false; |
| 1712 | + batteryLevel = &stm32wlLevel; |
| 1713 | + return true; |
| 1714 | +} |
| 1715 | + |
| 1716 | +#else |
| 1717 | +/** |
| 1718 | + * The STM32WL battery level sensor is unavailable - default to AnalogBatteryLevel |
| 1719 | + */ |
| 1720 | +bool Power::stm32wlInit() |
| 1721 | +{ |
| 1722 | + return false; |
| 1723 | +} |
| 1724 | +#endif |
| 1725 | + |
1605 | 1726 | #ifdef HAS_SERIAL_BATTERY_LEVEL |
1606 | 1727 | #include <SoftwareSerial.h> |
1607 | 1728 |
|
|
0 commit comments