From 424569b4c4bf112a3939724b30bad528a76a2b08 Mon Sep 17 00:00:00 2001
From: Scott Lahteine <thinkyhead@users.noreply.github.com>
Date: Thu, 18 Jun 2020 15:23:03 -0500
Subject: [PATCH] Power monitor and display (#17437)

---
 Marlin/Configuration_adv.h                    |  17 +++
 Marlin/src/HAL/AVR/HAL.h                      |   1 +
 Marlin/src/HAL/DUE/HAL.h                      |   3 +-
 Marlin/src/HAL/ESP32/HAL.h                    |   3 +-
 Marlin/src/HAL/LINUX/HAL.h                    |   3 +-
 Marlin/src/HAL/LPC1768/HAL.h                  |   2 +
 Marlin/src/HAL/SAMD51/HAL.h                   |   1 +
 Marlin/src/HAL/STM32/HAL.h                    |   3 +-
 Marlin/src/HAL/STM32F1/HAL.cpp                |  23 ++-
 Marlin/src/HAL/STM32F1/HAL.h                  |   3 +-
 Marlin/src/HAL/STM32_F4_F7/HAL.h              |   3 +-
 Marlin/src/HAL/TEENSY31_32/HAL.h              |   3 +-
 Marlin/src/HAL/TEENSY35_36/HAL.h              |   3 +-
 Marlin/src/core/macros.h                      |   3 +
 Marlin/src/feature/power_monitor.cpp          |  74 +++++++++
 Marlin/src/feature/power_monitor.h            | 140 ++++++++++++++++++
 .../src/gcode/feature/power_monitor/M430.cpp  |  70 +++++++++
 Marlin/src/gcode/gcode.cpp                    |   4 +
 Marlin/src/gcode/gcode.h                      |   3 +
 Marlin/src/inc/Conditionals_adv.h             |  11 ++
 Marlin/src/inc/Conditionals_post.h            |   7 +
 Marlin/src/inc/SanityCheck.h                  |  11 ++
 Marlin/src/lcd/dogm/status_screen_DOGM.cpp    |  89 +++++++++--
 Marlin/src/lcd/language/language_en.h         |   4 +
 Marlin/src/lcd/menu/menu_main.cpp             |  16 +-
 Marlin/src/lcd/menu/menu_power_monitor.cpp    |  62 ++++++++
 Marlin/src/lcd/ultralcd.cpp                   |   5 +-
 Marlin/src/libs/numtostr.cpp                  |  21 +++
 Marlin/src/libs/numtostr.h                    |   6 +
 Marlin/src/module/configuration_store.cpp     |  40 +++++
 Marlin/src/module/temperature.cpp             |  38 ++++-
 Marlin/src/module/temperature.h               |   8 +
 32 files changed, 652 insertions(+), 28 deletions(-)
 create mode 100644 Marlin/src/feature/power_monitor.cpp
 create mode 100644 Marlin/src/feature/power_monitor.h
 create mode 100644 Marlin/src/gcode/feature/power_monitor/M430.cpp
 create mode 100644 Marlin/src/lcd/menu/menu_power_monitor.cpp

diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h
index b63253a27c..d3dbc83a3e 100644
--- a/Marlin/Configuration_adv.h
+++ b/Marlin/Configuration_adv.h
@@ -2984,6 +2984,23 @@
   //#define FILAMENT_LCD_DISPLAY
 #endif
 
+/**
+ * Power Monitor
+ * Monitor voltage (V) and/or current (A), and -when possible- power (W)
+ *
+ * Read and configure with M430
+ *
+ * The current sensor feeds DC voltage (relative to the measured current) to an analog pin
+ * The voltage sensor feeds DC voltage (relative to the measured voltage) to an analog pin
+ */
+//#define POWER_MONITOR_CURRENT   // Monitor the system current
+//#define POWER_MONITOR_VOLTAGE   // Monitor the system voltage
+#if EITHER(POWER_MONITOR_CURRENT, POWER_MONITOR_VOLTAGE)
+  #define POWER_MONITOR_VOLTS_PER_AMP   0.05000   // Input voltage to the MCU analog pin per amp  - DO NOT apply more than ADC_VREF!
+  #define POWER_MONITOR_VOLTS_PER_VOLT  0.11786   // Input voltage to the MCU analog pin per volt - DO NOT apply more than ADC_VREF!
+  #define POWER_MONITOR_FIXED_VOLTAGE   13.6      // Voltage for a current sensor with no voltage sensor (for power display)
+#endif
+
 /**
  * CNC Coordinate Systems
  *
diff --git a/Marlin/src/HAL/AVR/HAL.h b/Marlin/src/HAL/AVR/HAL.h
index b7e05a956d..63cb3949aa 100644
--- a/Marlin/src/HAL/AVR/HAL.h
+++ b/Marlin/src/HAL/AVR/HAL.h
@@ -162,6 +162,7 @@ inline void HAL_adc_init() {
   #define HAL_START_ADC(ch) ADCSRB = 0; SET_ADMUX_ADCSRA(ch)
 #endif
 
+#define HAL_ADC_VREF        5.0
 #define HAL_ADC_RESOLUTION 10
 #define HAL_READ_ADC()  ADC
 #define HAL_ADC_READY() !TEST(ADCSRA, ADSC)
diff --git a/Marlin/src/HAL/DUE/HAL.h b/Marlin/src/HAL/DUE/HAL.h
index 7410b2a1ee..bc60b1d6d1 100644
--- a/Marlin/src/HAL/DUE/HAL.h
+++ b/Marlin/src/HAL/DUE/HAL.h
@@ -143,8 +143,9 @@ extern uint16_t HAL_adc_result;     // result of last ADC conversion
 
 inline void HAL_adc_init() {}//todo
 
-#define HAL_START_ADC(ch)   HAL_adc_start_conversion(ch)
+#define HAL_ADC_VREF         3.3
 #define HAL_ADC_RESOLUTION  10
+#define HAL_START_ADC(ch)   HAL_adc_start_conversion(ch)
 #define HAL_READ_ADC()      HAL_adc_result
 #define HAL_ADC_READY()     true
 
diff --git a/Marlin/src/HAL/ESP32/HAL.h b/Marlin/src/HAL/ESP32/HAL.h
index 9eed6d9461..3b752914b1 100644
--- a/Marlin/src/HAL/ESP32/HAL.h
+++ b/Marlin/src/HAL/ESP32/HAL.h
@@ -112,8 +112,9 @@ void analogWrite(pin_t pin, int value);
 
 void HAL_adc_init();
 
-#define HAL_START_ADC(pin)  HAL_adc_start_conversion(pin)
+#define HAL_ADC_VREF         3.3
 #define HAL_ADC_RESOLUTION  10
+#define HAL_START_ADC(pin)  HAL_adc_start_conversion(pin)
 #define HAL_READ_ADC()      HAL_adc_result
 #define HAL_ADC_READY()     true
 
diff --git a/Marlin/src/HAL/LINUX/HAL.h b/Marlin/src/HAL/LINUX/HAL.h
index ba9a785ce1..0573b334c4 100644
--- a/Marlin/src/HAL/LINUX/HAL.h
+++ b/Marlin/src/HAL/LINUX/HAL.h
@@ -86,9 +86,10 @@ int freeMemory();
 #pragma GCC diagnostic pop
 
 // ADC
+#define HAL_ADC_VREF           5.0
+#define HAL_ADC_RESOLUTION    10
 #define HAL_ANALOG_SELECT(ch) HAL_adc_enable_channel(ch)
 #define HAL_START_ADC(ch)     HAL_adc_start_conversion(ch)
-#define HAL_ADC_RESOLUTION    10
 #define HAL_READ_ADC()        HAL_adc_get_result()
 #define HAL_ADC_READY()       true
 
diff --git a/Marlin/src/HAL/LPC1768/HAL.h b/Marlin/src/HAL/LPC1768/HAL.h
index 66c55bb718..0f9f227bba 100644
--- a/Marlin/src/HAL/LPC1768/HAL.h
+++ b/Marlin/src/HAL/LPC1768/HAL.h
@@ -150,6 +150,8 @@ int freeMemory();
                                     // K = 6, 565 samples, 500Hz sample rate, 1.13s convergence on full range step
                                     // Memory usage per ADC channel (bytes): 4 (32 Bytes for 8 channels)
 
+#define HAL_ADC_VREF            3.3 // ADC voltage reference
+
 #define HAL_ADC_RESOLUTION     12   // 15 bit maximum, raw temperature is stored as int16_t
 #define HAL_ADC_FILTERED            // Disable oversampling done in Marlin as ADC values already filtered in HAL
 
diff --git a/Marlin/src/HAL/SAMD51/HAL.h b/Marlin/src/HAL/SAMD51/HAL.h
index 0829a2ccdc..e72b7a3d82 100644
--- a/Marlin/src/HAL/SAMD51/HAL.h
+++ b/Marlin/src/HAL/SAMD51/HAL.h
@@ -122,6 +122,7 @@ extern uint16_t HAL_adc_result;     // Most recent ADC conversion
 void HAL_adc_init();
 
 //#define HAL_ADC_FILTERED          // Disable Marlin's oversampling. The HAL filters ADC values.
+#define HAL_ADC_VREF         3.3
 #define HAL_ADC_RESOLUTION  10      // ... 12
 #define HAL_START_ADC(pin)  HAL_adc_start_conversion(pin)
 #define HAL_READ_ADC()      HAL_adc_result
diff --git a/Marlin/src/HAL/STM32/HAL.h b/Marlin/src/HAL/STM32/HAL.h
index 4046f67044..2f37a5ec73 100644
--- a/Marlin/src/HAL/STM32/HAL.h
+++ b/Marlin/src/HAL/STM32/HAL.h
@@ -199,8 +199,9 @@ static inline int freeMemory() {
 
 inline void HAL_adc_init() {}
 
-#define HAL_START_ADC(pin)  HAL_adc_start_conversion(pin)
+#define HAL_ADC_VREF         3.3
 #define HAL_ADC_RESOLUTION  10
+#define HAL_START_ADC(pin)  HAL_adc_start_conversion(pin)
 #define HAL_READ_ADC()      HAL_adc_result
 #define HAL_ADC_READY()     true
 
diff --git a/Marlin/src/HAL/STM32F1/HAL.cpp b/Marlin/src/HAL/STM32F1/HAL.cpp
index 01fd2c8fc3..6588829438 100644
--- a/Marlin/src/HAL/STM32F1/HAL.cpp
+++ b/Marlin/src/HAL/STM32F1/HAL.cpp
@@ -139,9 +139,15 @@ const uint8_t adc_pins[] = {
   #if HAS_JOY_ADC_Z
     JOY_Z_PIN,
   #endif
+  #if ENABLED(POWER_MONITOR_CURRENT)
+    POWER_MONITOR_CURRENT_PIN,
+  #endif
+  #if ENABLED(POWER_MONITOR_VOLTAGE)
+    POWER_MONITOR_VOLTAGE_PIN,
+  #endif
 };
 
-enum TEMP_PINS : char {
+enum TempPinIndex : char {
   #if HAS_TEMP_ADC_0
     TEMP_0,
   #endif
@@ -187,6 +193,12 @@ enum TEMP_PINS : char {
   #if HAS_JOY_ADC_Z
     JOY_Z,
   #endif
+  #if ENABLED(POWER_MONITOR_CURRENT)
+    POWERMON_CURRENT,
+  #endif
+  #if ENABLED(POWER_MONITOR_VOLTAGE)
+    POWERMON_VOLTS,
+  #endif
   ADC_PIN_COUNT
 };
 
@@ -323,7 +335,8 @@ void HAL_adc_init() {
 }
 
 void HAL_adc_start_conversion(const uint8_t adc_pin) {
-  TEMP_PINS pin_index;
+  //TEMP_PINS pin_index;
+  TempPinIndex pin_index;
   switch (adc_pin) {
     default: return;
     #if HAS_TEMP_ADC_0
@@ -371,6 +384,12 @@ void HAL_adc_start_conversion(const uint8_t adc_pin) {
     #if ENABLED(ADC_KEYPAD)
       case ADC_KEYPAD_PIN: pin_index = ADC_KEY; break;
     #endif
+    #if ENABLED(POWER_MONITOR_CURRENT)
+      case POWER_MONITOR_CURRENT_PIN: pin_index = POWERMON_CURRENT; break;
+    #endif
+    #if ENABLED(POWER_MONITOR_VOLTAGE)
+      case POWER_MONITOR_VOLTAGE_PIN: pin_index = POWERMON_VOLTS; break;
+    #endif
   }
   HAL_adc_result = (HAL_adc_results[(int)pin_index] >> 2) & 0x3FF; // shift to get 10 bits only.
 }
diff --git a/Marlin/src/HAL/STM32F1/HAL.h b/Marlin/src/HAL/STM32F1/HAL.h
index 49769a59d8..6550fbe224 100644
--- a/Marlin/src/HAL/STM32F1/HAL.h
+++ b/Marlin/src/HAL/STM32F1/HAL.h
@@ -255,8 +255,9 @@ static int freeMemory() {
 
 void HAL_adc_init();
 
-#define HAL_START_ADC(pin)  HAL_adc_start_conversion(pin)
+#define HAL_ADC_VREF         3.3
 #define HAL_ADC_RESOLUTION  10
+#define HAL_START_ADC(pin)  HAL_adc_start_conversion(pin)
 #define HAL_READ_ADC()      HAL_adc_result
 #define HAL_ADC_READY()     true
 
diff --git a/Marlin/src/HAL/STM32_F4_F7/HAL.h b/Marlin/src/HAL/STM32_F4_F7/HAL.h
index aa8575e6a8..07e56b24b4 100644
--- a/Marlin/src/HAL/STM32_F4_F7/HAL.h
+++ b/Marlin/src/HAL/STM32_F4_F7/HAL.h
@@ -219,8 +219,9 @@ static inline int freeMemory() {
 
 inline void HAL_adc_init() {}
 
-#define HAL_START_ADC(pin)  HAL_adc_start_conversion(pin)
+#define HAL_ADC_VREF         3.3
 #define HAL_ADC_RESOLUTION  10
+#define HAL_START_ADC(pin)  HAL_adc_start_conversion(pin)
 #define HAL_READ_ADC()      HAL_adc_result
 #define HAL_ADC_READY()     true
 
diff --git a/Marlin/src/HAL/TEENSY31_32/HAL.h b/Marlin/src/HAL/TEENSY31_32/HAL.h
index 23e4c18571..3356ec2dff 100644
--- a/Marlin/src/HAL/TEENSY31_32/HAL.h
+++ b/Marlin/src/HAL/TEENSY31_32/HAL.h
@@ -107,8 +107,9 @@ extern "C" {
 
 void HAL_adc_init();
 
-#define HAL_START_ADC(pin)  HAL_adc_start_conversion(pin)
+#define HAL_ADC_VREF         3.3
 #define HAL_ADC_RESOLUTION  10
+#define HAL_START_ADC(pin)  HAL_adc_start_conversion(pin)
 #define HAL_READ_ADC()      HAL_adc_get_result()
 #define HAL_ADC_READY()     true
 
diff --git a/Marlin/src/HAL/TEENSY35_36/HAL.h b/Marlin/src/HAL/TEENSY35_36/HAL.h
index 7aa10abe95..1d912e3f60 100644
--- a/Marlin/src/HAL/TEENSY35_36/HAL.h
+++ b/Marlin/src/HAL/TEENSY35_36/HAL.h
@@ -112,8 +112,9 @@ extern "C" {
 
 void HAL_adc_init();
 
-#define HAL_START_ADC(pin)  HAL_adc_start_conversion(pin)
+#define HAL_ADC_VREF         3.3
 #define HAL_ADC_RESOLUTION  10
+#define HAL_START_ADC(pin)  HAL_adc_start_conversion(pin)
 #define HAL_READ_ADC()      HAL_adc_get_result()
 #define HAL_ADC_READY()     true
 
diff --git a/Marlin/src/core/macros.h b/Marlin/src/core/macros.h
index ebcb9ebcfb..cf0fc2c5db 100644
--- a/Marlin/src/core/macros.h
+++ b/Marlin/src/core/macros.h
@@ -97,10 +97,13 @@
   #define CBI(A,B) (A &= ~(1 << (B)))
 #endif
 
+#define TBI(N,B) (N ^= _BV(B))
+
 #define _BV32(b) (1UL << (b))
 #define TEST32(n,b) !!((n)&_BV32(b))
 #define SBI32(n,b) (n |= _BV32(b))
 #define CBI32(n,b) (n &= ~_BV32(b))
+#define TBI32(N,B) (N ^= _BV32(B))
 
 #define cu(x)      ({__typeof__(x) _x = (x); (_x)*(_x)*(_x);})
 #define RADIANS(d) ((d)*float(M_PI)/180.0f)
diff --git a/Marlin/src/feature/power_monitor.cpp b/Marlin/src/feature/power_monitor.cpp
new file mode 100644
index 0000000000..30b19a99e1
--- /dev/null
+++ b/Marlin/src/feature/power_monitor.cpp
@@ -0,0 +1,74 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../inc/MarlinConfigPre.h"
+
+#if HAS_POWER_MONITOR
+
+#include "power_monitor.h"
+
+#include "../lcd/ultralcd.h"
+#include "../lcd/lcdprint.h"
+
+uint8_t PowerMonitor::flags; // = 0
+
+#if ENABLED(POWER_MONITOR_CURRENT)
+  pm_lpf_t<PowerMonitor::amps_adc_scale, PM_K_VALUE, PM_K_SCALE> PowerMonitor::amps;
+#endif
+#if ENABLED(POWER_MONITOR_VOLTAGE)
+  pm_lpf_t<PowerMonitor::volts_adc_scale, PM_K_VALUE, PM_K_SCALE> PowerMonitor::volts;
+#endif
+
+millis_t PowerMonitor::display_item_ms;
+uint8_t PowerMonitor::display_item;
+
+PowerMonitor power_monitor; // Single instance - this calls the constructor
+
+#if HAS_GRAPHICAL_LCD
+
+  #if ENABLED(POWER_MONITOR_CURRENT)
+    void PowerMonitor::draw_current() {
+      const float amps = getAmps();
+      lcd_put_u8str(amps < 100 ? ftostr21ns(amps) : ui16tostr4((uint16_t)amps));
+      lcd_put_wchar('A');
+    }
+  #endif
+
+  #if HAS_POWER_MONITOR_VREF
+    void PowerMonitor::draw_voltage() {
+      const float volts = getVolts();
+      lcd_put_u8str(volts < 100 ? ftostr21ns(volts) : ui16tostr4((uint16_t)volts));
+      lcd_put_wchar('V');
+    }
+  #endif
+
+  #if HAS_POWER_MONITOR_WATTS
+    void PowerMonitor::draw_power() {
+      const float power = getPower();
+      lcd_put_u8str(power < 100 ? ftostr21ns(power) : ui16tostr4((uint16_t)power));
+      lcd_put_wchar('W');
+    }
+  #endif
+
+#endif // HAS_GRAPHICAL_LCD
+
+#endif // HAS_POWER_MONITOR
diff --git a/Marlin/src/feature/power_monitor.h b/Marlin/src/feature/power_monitor.h
new file mode 100644
index 0000000000..fc7a23b8f3
--- /dev/null
+++ b/Marlin/src/feature/power_monitor.h
@@ -0,0 +1,140 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include "../inc/MarlinConfig.h"
+
+#define PM_SAMPLE_RANGE 1024
+#define PM_K_VALUE      6
+#define PM_K_SCALE      6
+
+template <const float & SCALE, int K_VALUE, int K_SCALE>
+struct pm_lpf_t {
+  uint32_t filter_buf;
+  float value;
+  void add_sample(const uint16_t sample) {
+    filter_buf = filter_buf - (filter_buf >> K_VALUE) + (uint32_t(sample) << K_SCALE);
+  }
+  void capture() {
+    value = filter_buf * (SCALE * (1.0f / (1UL << (PM_K_VALUE + PM_K_SCALE))));
+  }
+  void reset(uint16_t reset_value = 0) {
+    filter_buf = uint32_t(reset_value) << (K_VALUE + K_SCALE);
+    capture();
+  }
+};
+
+class PowerMonitor {
+private:
+  #if ENABLED(POWER_MONITOR_CURRENT)
+    static constexpr float amps_adc_scale = float(ADC_VREF) / (POWER_MONITOR_VOLTS_PER_AMP * PM_SAMPLE_RANGE);
+    static pm_lpf_t<amps_adc_scale, PM_K_VALUE, PM_K_SCALE> amps;
+  #endif
+  #if ENABLED(POWER_MONITOR_VOLTAGE)
+    static constexpr float volts_adc_scale = float(ADC_VREF) / (POWER_MONITOR_VOLTS_PER_VOLT * PM_SAMPLE_RANGE);
+    static pm_lpf_t<volts_adc_scale, PM_K_VALUE, PM_K_SCALE> volts;
+  #endif
+
+public:
+  static uint8_t flags;  // M430 flags to display current
+
+  static millis_t display_item_ms;
+  static uint8_t display_item;
+
+  PowerMonitor() { reset(); }
+
+  enum PM_Display_Bit : uint8_t {
+    PM_DISP_BIT_I, // Current display enable bit
+    PM_DISP_BIT_V, // Voltage display enable bit
+    PM_DISP_BIT_P  // Power display enable bit
+  };
+
+  #if ENABLED(POWER_MONITOR_CURRENT)
+    FORCE_INLINE static float getAmps() { return amps.value; }
+    void add_current_sample(const uint16_t value) { amps.add_sample(value); }
+  #endif
+
+  #if HAS_POWER_MONITOR_VREF
+    #if ENABLED(POWER_MONITOR_VOLTAGE)
+      FORCE_INLINE static float getVolts() { return volts.value; }
+    #else
+      FORCE_INLINE static float getVolts() { return POWER_MONITOR_FIXED_VOLTAGE; }  // using a specified fixed valtage as the voltage measurement
+    #endif
+    #if ENABLED(POWER_MONITOR_VOLTAGE)
+      void add_voltage_sample(const uint16_t value) { volts.add_sample(value); }
+    #endif
+  #endif
+
+  #if HAS_POWER_MONITOR_WATTS
+    FORCE_INLINE static float getPower() { return getAmps() * getVolts(); }
+  #endif
+
+  #if HAS_SPI_LCD
+    FORCE_INLINE static bool display_enabled() { return flags != 0x00; }
+    #if ENABLED(POWER_MONITOR_CURRENT)
+      static void draw_current();
+      FORCE_INLINE static bool current_display_enabled() { return TEST(flags, PM_DISP_BIT_I); }
+      FORCE_INLINE static void set_current_display(const bool b) { SET_BIT_TO(flags, PM_DISP_BIT_I, b); }
+      FORCE_INLINE static void toggle_current_display() { TBI(flags, PM_DISP_BIT_I); }
+    #endif
+    #if HAS_POWER_MONITOR_VREF
+      static void draw_voltage();
+      FORCE_INLINE static bool voltage_display_enabled() { return TEST(flags, PM_DISP_BIT_V); }
+      FORCE_INLINE static void set_voltage_display(const bool b) { SET_BIT_TO(flags, PM_DISP_BIT_V, b); }
+      FORCE_INLINE static void toggle_voltage_display() { TBI(flags, PM_DISP_BIT_I); }
+    #endif
+    #if HAS_POWER_MONITOR_WATTS
+      static void draw_power();
+      FORCE_INLINE static bool power_display_enabled() { return TEST(flags, PM_DISP_BIT_P); }
+      FORCE_INLINE static void set_power_display(const bool b) { SET_BIT_TO(flags, PM_DISP_BIT_P, b); }
+      FORCE_INLINE static void toggle_power_display() { TBI(flags, PM_DISP_BIT_I); }
+    #endif
+  #endif
+
+  static void reset() {
+    flags = 0x00;
+
+    #if ENABLED(POWER_MONITOR_CURRENT)
+      amps.reset();
+    #endif
+
+    #if ENABLED(POWER_MONITOR_VOLTAGE)
+      volts.reset();
+    #endif
+
+    #if ENABLED(SDSUPPORT)
+      display_item_ms = 0;
+      display_item = 0;
+    #endif
+  }
+
+  static void capture_values() {
+    #if ENABLED(POWER_MONITOR_CURRENT)
+     amps.capture();
+    #endif
+    #if ENABLED(POWER_MONITOR_VOLTAGE)
+      volts.capture();
+    #endif
+  }
+};
+
+extern PowerMonitor power_monitor;
diff --git a/Marlin/src/gcode/feature/power_monitor/M430.cpp b/Marlin/src/gcode/feature/power_monitor/M430.cpp
new file mode 100644
index 0000000000..50bb146c78
--- /dev/null
+++ b/Marlin/src/gcode/feature/power_monitor/M430.cpp
@@ -0,0 +1,70 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if HAS_POWER_MONITOR
+
+#include "../../../feature/power_monitor.h"
+#include "../../../Marlin.h"
+#include "../../gcode.h"
+
+/**
+ * M430: Enable/disable current LCD display
+ *       With no parameters report the system current draw (in Amps)
+ *
+ *  I[bool] - Set Display of current on the LCD
+ *  V[bool] - Set Display of voltage on the LCD
+ *  W[bool] - Set Display of power on the LCD
+ */
+void GcodeSuite::M430() {
+  bool do_report = true;
+  #if HAS_SPI_LCD
+    #if ENABLED(POWER_MONITOR_CURRENT)
+      if (parser.seen('I')) { power_monitor.set_current_display(parser.value_bool()); do_report = false; }
+    #endif
+    #if HAS_POWER_MONITOR_VREF
+      if (parser.seen('V')) { power_monitor.set_voltage_display(parser.value_bool()); do_report = false; }
+    #endif
+    #if HAS_POWER_MONITOR_WATTS
+      if (parser.seen('W')) { power_monitor.set_power_display(parser.value_bool()); do_report = false; }
+    #endif
+  #endif
+  if (do_report) {
+    SERIAL_ECHOLNPAIR(
+      #if ENABLED(POWER_MONITOR_CURRENT)
+        "Current: ", power_monitor.getAmps(), "A"
+        #if HAS_POWER_MONITOR_VREF
+          "  "
+        #endif
+      #endif
+      #if HAS_POWER_MONITOR_VREF
+        "Voltage: ", power_monitor.getVolts(), "V"
+      #endif
+      #if HAS_POWER_MONITOR_WATTS
+        "  Power: ", power_monitor.getPower(), "W"
+      #endif
+    );
+  }
+}
+
+#endif // HAS_POWER_MONITOR
diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp
index f543342e81..859826ca2e 100644
--- a/Marlin/src/gcode/gcode.cpp
+++ b/Marlin/src/gcode/gcode.cpp
@@ -720,6 +720,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
         case 428: M428(); break;                                  // M428: Apply current_position to home_offset
       #endif
 
+      #if HAS_POWER_MONITOR
+        case 430: M430(); break;                                  // M430: Read the system current (A), voltage (V), and power (W)
+      #endif
+
       #if ENABLED(CANCEL_OBJECTS)
         case 486: M486(); break;                                  // M486: Identify and cancel objects
       #endif
diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h
index 573ef0f625..06fcce0fa3 100644
--- a/Marlin/src/gcode/gcode.h
+++ b/Marlin/src/gcode/gcode.h
@@ -217,6 +217,7 @@
  * M422 - Set Z Stepper automatic alignment position using probe. X<units> Y<units> A<axis> (Requires Z_STEPPER_AUTO_ALIGN)
  * M425 - Enable/Disable and tune backlash correction. (Requires BACKLASH_COMPENSATION and BACKLASH_GCODE)
  * M428 - Set the home_offset based on the current_position. Nearest edge applies. (Disabled by NO_WORKSPACE_OFFSETS or DELTA)
+ * M430 - Read the system current, voltage, and power (Requires POWER_MONITOR_CURRENT, POWER_MONITOR_VOLTAGE, or POWER_MONITOR_FIXED_VOLTAGE)
  * M486 - Identify and cancel objects. (Requires CANCEL_OBJECTS)
  * M500 - Store parameters in EEPROM. (Requires EEPROM_SETTINGS)
  * M501 - Restore parameters from EEPROM. (Requires EEPROM_SETTINGS)
@@ -735,6 +736,8 @@ private:
 
   TERN_(HAS_M206_COMMAND, static void M428());
 
+  TERN_(HAS_POWER_MONITOR, static void M430());
+
   TERN_(CANCEL_OBJECTS, static void M486());
 
   static void M500();
diff --git a/Marlin/src/inc/Conditionals_adv.h b/Marlin/src/inc/Conditionals_adv.h
index 781c139f28..a318b198a8 100644
--- a/Marlin/src/inc/Conditionals_adv.h
+++ b/Marlin/src/inc/Conditionals_adv.h
@@ -352,6 +352,17 @@
   #define SD_CONNECTION_IS(...) 0
 #endif
 
+// Power Monitor sensors
+#if EITHER(POWER_MONITOR_CURRENT, POWER_MONITOR_VOLTAGE)
+  #define HAS_POWER_MONITOR 1
+#endif
+#if ENABLED(POWER_MONITOR_VOLTAGE) || defined(POWER_MONITOR_FIXED_VOLTAGE)
+  #define HAS_POWER_MONITOR_VREF 1
+#endif
+#if BOTH(HAS_POWER_MONITOR_VREF, POWER_MONITOR_CURRENT)
+  #define HAS_POWER_MONITOR_WATTS 1
+#endif
+
 // Flag if an EEPROM type is pre-selected
 #if ENABLED(EEPROM_SETTINGS) && NONE(I2C_EEPROM, SPI_EEPROM, QSPI_EEPROM, FLASH_EEPROM_EMULATION, SRAM_EEPROM_EMULATION, SDCARD_EEPROM_EMULATION)
   #define NO_EEPROM_SELECTED 1
diff --git a/Marlin/src/inc/Conditionals_post.h b/Marlin/src/inc/Conditionals_post.h
index 73d878de22..228b21516b 100644
--- a/Marlin/src/inc/Conditionals_post.h
+++ b/Marlin/src/inc/Conditionals_post.h
@@ -30,6 +30,13 @@
   // Extras for CI testing
 #endif
 
+// ADC
+#ifdef BOARD_ADC_VREF
+  #define ADC_VREF BOARD_ADC_VREF
+#else
+  #define ADC_VREF HAL_ADC_VREF
+#endif
+
 // Linear advance uses Jerk since E is an isolated axis
 #if BOTH(HAS_JUNCTION_DEVIATION, LIN_ADVANCE)
   #define HAS_LINEAR_E_JERK 1
diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h
index 4c42c7f179..605a4afb6f 100644
--- a/Marlin/src/inc/SanityCheck.h
+++ b/Marlin/src/inc/SanityCheck.h
@@ -1480,6 +1480,17 @@ static_assert(hbm[Z_AXIS] >= 0, "HOMING_BUMP_MM.Z must be greater than or equal
   #endif
 #endif
 
+/**
+ * System Power Sensor
+ */
+#if ENABLED(POWER_MONITOR_CURRENT) && !PIN_EXISTS(POWER_MONITOR_CURRENT)
+  #error "POWER_MONITOR_CURRENT requires a valid POWER_MONITOR_CURRENT_PIN."
+#elif ENABLED(POWER_MONITOR_VOLTAGE) && !PIN_EXISTS(POWER_MONITOR_VOLTAGE)
+  #error "POWER_MONITOR_VOLTAGE requires POWER_MONITOR_VOLTAGE_PIN to be defined."
+#elif BOTH(POWER_MONITOR_CURRENT, POWER_MONITOR_VOLTAGE) && POWER_MONITOR_CURRENT_PIN == POWER_MONITOR_VOLTAGE_PIN
+  #error "POWER_MONITOR_CURRENT_PIN and POWER_MONITOR_VOLTAGE_PIN must be different."
+#endif
+
 /**
  * Volumetric Extruder Limit
  */
diff --git a/Marlin/src/lcd/dogm/status_screen_DOGM.cpp b/Marlin/src/lcd/dogm/status_screen_DOGM.cpp
index 6fc6b2f614..72bdf1e96a 100644
--- a/Marlin/src/lcd/dogm/status_screen_DOGM.cpp
+++ b/Marlin/src/lcd/dogm/status_screen_DOGM.cpp
@@ -48,6 +48,10 @@
   #include "../../feature/spindle_laser.h"
 #endif
 
+#if HAS_POWER_MONITOR
+  #include "../../feature/power_monitor.h"
+#endif
+
 #if ENABLED(SDSUPPORT)
   #include "../../sd/cardreader.h"
 #endif
@@ -103,6 +107,59 @@
   #define STATUS_HEATERS_BOT (STATUS_HEATERS_Y + STATUS_HEATERS_HEIGHT - 1)
 #endif
 
+#if HAS_POWER_MONITOR
+
+  void display_power_monitor(const uint8_t x, const uint8_t y) {
+
+    lcd_moveto(x, y);
+
+    #if ENABLED(POWER_MONITOR_CURRENT)
+      const bool iflag = power_monitor.current_display_enabled();
+    #endif
+    #if HAS_POWER_MONITOR_VREF
+      const bool vflag = power_monitor.voltage_display_enabled();
+    #endif
+    #if HAS_POWER_MONITOR_WATTS
+      const bool wflag = power_monitor.power_display_enabled();
+    #endif
+
+    #if ENABLED(POWER_MONITOR_CURRENT) || HAS_POWER_MONITOR_VREF
+      // cycle between current, voltage, and power
+      if (ELAPSED(millis(), power_monitor.display_item_ms)) {
+        power_monitor.display_item_ms = millis() + 1000UL;
+        ++power_monitor.display_item;
+      }
+    #endif
+
+    // ensure we have the right one selected for display
+    for (uint8_t i = 0; i < 3; i++) {
+      #if ENABLED(POWER_MONITOR_CURRENT)
+        if (power_monitor.display_item == 0 && !iflag) ++power_monitor.display_item;
+      #endif
+      #if HAS_POWER_MONITOR_VREF
+        if (power_monitor.display_item == 1 && !vflag) ++power_monitor.display_item;
+      #endif
+      #if ENABLED(POWER_MONITOR_CURRENT)
+        if (power_monitor.display_item == 2 && !wflag) ++power_monitor.display_item;
+      #endif
+      if (power_monitor.display_item >= 3) power_monitor.display_item = 0;
+    }
+
+    switch (power_monitor.display_item) {
+      #if ENABLED(POWER_MONITOR_CURRENT)                // Current
+        case 0: if (iflag) power_monitor.draw_current(); break;
+      #endif
+      #if HAS_POWER_MONITOR_VREF                        // Voltage
+        case 1: if (vflag) power_monitor.draw_voltage(); break;
+      #endif
+      #if HAS_POWER_MONITOR_WATTS                       // Power
+        case 2: if (wflag) power_monitor.draw_power(); break;
+      #endif
+      default: break;
+    }
+  }
+#endif
+
 #define PROGRESS_BAR_X 54
 #define PROGRESS_BAR_Y (EXTRAS_BASELINE + 1)
 #define PROGRESS_BAR_WIDTH (LCD_PIXEL_WIDTH - PROGRESS_BAR_X)
@@ -787,16 +844,25 @@ void MarlinUI::draw_status_screen() {
 void MarlinUI::draw_status_message(const bool blink) {
 
   // Get the UTF8 character count of the string
-  uint8_t slen = utf8_strlen(status_message);
+  uint8_t lcd_width = LCD_WIDTH, pixel_width = LCD_PIXEL_WIDTH,
+          slen = utf8_strlen(status_message);
+
+  #if HAS_POWER_MONITOR
+    if (power_monitor.display_enabled()) {
+      // make room at the end of the status line for the power monitor reading
+      lcd_width -= 6;
+      pixel_width -= (MENU_FONT_WIDTH) * 6;
+    }
+  #endif
 
   #if ENABLED(STATUS_MESSAGE_SCROLLING)
 
     static bool last_blink = false;
 
-    if (slen <= LCD_WIDTH) {
+    if (slen <= lcd_width) {
       // The string fits within the line. Print with no scrolling
       lcd_put_u8str(status_message);
-      while (slen < LCD_WIDTH) { lcd_put_wchar(' '); ++slen; }
+      while (slen < lcd_width) { lcd_put_wchar(' '); ++slen; }
     }
     else {
       // String is longer than the available space
@@ -805,20 +871,21 @@ void MarlinUI::draw_status_message(const bool blink) {
       // and the string remaining length
       uint8_t rlen;
       const char *stat = status_and_len(rlen);
-      lcd_put_u8str_max(stat, LCD_PIXEL_WIDTH);
+      lcd_put_u8str_max(stat, pixel_width);
 
       // If the remaining string doesn't completely fill the screen
-      if (rlen < LCD_WIDTH) {
+      if (rlen < lcd_width) {
         lcd_put_wchar('.');                     // Always at 1+ spaces left, draw a dot
-        uint8_t chars = LCD_WIDTH - rlen;       // Amount of space left in characters
+        uint8_t chars = lcd_width - rlen;       // Amount of space left in characters
         if (--chars) {                          // Draw a second dot if there's space
           lcd_put_wchar('.');
           if (--chars) {                        // Print a second copy of the message
-            lcd_put_u8str_max(status_message, LCD_PIXEL_WIDTH - (rlen + 2) * (MENU_FONT_WIDTH));
+            lcd_put_u8str_max(status_message, pixel_width - (rlen + 2) * (MENU_FONT_WIDTH));
             lcd_put_wchar(' ');
           }
         }
       }
+
       if (last_blink != blink) {
         last_blink = blink;
         advance_status_scroll();
@@ -830,12 +897,16 @@ void MarlinUI::draw_status_message(const bool blink) {
     UNUSED(blink);
 
     // Just print the string to the LCD
-    lcd_put_u8str_max(status_message, LCD_PIXEL_WIDTH);
+    lcd_put_u8str_max(status_message, pixel_width);
 
     // Fill the rest with spaces
-    for (; slen < LCD_WIDTH; ++slen) lcd_put_wchar(' ');
+    for (; slen < lcd_width; ++slen) lcd_put_wchar(' ');
 
   #endif // !STATUS_MESSAGE_SCROLLING
+
+  #if HAS_POWER_MONITOR
+    display_power_monitor(pixel_width + MENU_FONT_WIDTH, STATUS_BASELINE);
+  #endif
 }
 
 #endif // HAS_GRAPHICAL_LCD && !LIGHTWEIGHT_UI
diff --git a/Marlin/src/lcd/language/language_en.h b/Marlin/src/lcd/language/language_en.h
index bfa1ed52ea..cb95205514 100644
--- a/Marlin/src/lcd/language/language_en.h
+++ b/Marlin/src/lcd/language/language_en.h
@@ -340,6 +340,10 @@ namespace Language_en {
   PROGMEM Language_Str MSG_INFO_SCREEN                     = _UxGT("Info Screen");
   PROGMEM Language_Str MSG_PREPARE                         = _UxGT("Prepare");
   PROGMEM Language_Str MSG_TUNE                            = _UxGT("Tune");
+  PROGMEM Language_Str MSG_POWER_MONITOR                   = _UxGT("Power monitor");
+  PROGMEM Language_Str MSG_CURRENT                         = _UxGT("Current");
+  PROGMEM Language_Str MSG_VOLTAGE                         = _UxGT("Voltage");
+  PROGMEM Language_Str MSG_POWER                           = _UxGT("Power");
   PROGMEM Language_Str MSG_START_PRINT                     = _UxGT("Start Print");
   PROGMEM Language_Str MSG_BUTTON_NEXT                     = _UxGT("Next");
   PROGMEM Language_Str MSG_BUTTON_INIT                     = _UxGT("Init");
diff --git a/Marlin/src/lcd/menu/menu_main.cpp b/Marlin/src/lcd/menu/menu_main.cpp
index ee46c6a2a4..7ef267ff6a 100644
--- a/Marlin/src/lcd/menu/menu_main.cpp
+++ b/Marlin/src/lcd/menu/menu_main.cpp
@@ -59,6 +59,14 @@ void menu_configuration();
   void menu_user();
 #endif
 
+#if HAS_POWER_MONITOR
+  void menu_power_monitor();
+#endif
+
+#if ENABLED(MIXING_EXTRUDER)
+  void menu_mixer();
+#endif
+
 #if ENABLED(ADVANCED_PAUSE_FEATURE)
   void _menu_temp_filament_op(const PauseMode, const int8_t);
   void menu_change_filament();
@@ -76,10 +84,6 @@ void menu_configuration();
   void menu_spindle_laser();
 #endif
 
-#if ENABLED(MIXING_EXTRUDER)
-  void menu_mixer();
-#endif
-
 extern const char M21_STR[];
 
 void menu_main() {
@@ -155,6 +159,10 @@ void menu_main() {
 
   SUBMENU(MSG_TEMPERATURE, menu_temperature);
 
+  #if HAS_POWER_MONITOR
+    MENU_ITEM(submenu, MSG_POWER_MONITOR, menu_power_monitor);
+  #endif
+
   #if ENABLED(MIXING_EXTRUDER)
     SUBMENU(MSG_MIXER, menu_mixer);
   #endif
diff --git a/Marlin/src/lcd/menu/menu_power_monitor.cpp b/Marlin/src/lcd/menu/menu_power_monitor.cpp
new file mode 100644
index 0000000000..7055f01c31
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_power_monitor.cpp
@@ -0,0 +1,62 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Power Monitor Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_LCD_MENU && HAS_POWER_MONITOR
+
+#include "menu.h"
+#include "../../feature/power_monitor.h"
+
+void menu_power_monitor() {
+  START_MENU();
+  MENU_BACK(MSG_MAIN);
+
+  #if ENABLED(POWER_MONITOR_CURRENT)
+  {
+    bool ena = power_monitor.current_display_enabled();
+    MENU_ITEM_EDIT_CALLBACK(bool, MSG_CURRENT, &ena, power_monitor.toggle_current_display);
+  }
+  #endif
+
+  #if HAS_POWER_MONITOR_VREF
+  {
+    bool ena = power_monitor.voltage_display_enabled();
+    MENU_ITEM_EDIT_CALLBACK(bool, MSG_VOLTAGE, &ena, power_monitor.toggle_voltage_display);
+  }
+  #endif
+
+  #if HAS_POWER_MONITOR_WATTS
+  {
+    bool ena = power_monitor.power_display_enabled();
+    MENU_ITEM_EDIT_CALLBACK(bool, MSG_POWER, &ena, power_monitor.toggle_power_display);
+  }
+  #endif
+
+  END_MENU();
+}
+
+#endif // HAS_LCD_MENU && HAS_POWER_MONITOR
diff --git a/Marlin/src/lcd/ultralcd.cpp b/Marlin/src/lcd/ultralcd.cpp
index 9fa5f3a67a..98f29804c8 100644
--- a/Marlin/src/lcd/ultralcd.cpp
+++ b/Marlin/src/lcd/ultralcd.cpp
@@ -112,6 +112,10 @@ MarlinUI ui;
   #include "../module/thermistor/thermistors.h"
 #endif
 
+#if HAS_POWER_MONITOR
+  #include "../feature/power_monitor.h"
+#endif
+
 #if HAS_ENCODER_ACTION
   volatile uint8_t MarlinUI::buttons;
   #if HAS_SLOW_BUTTONS
@@ -533,7 +537,6 @@ void MarlinUI::status_screen() {
   #endif // LCD_PROGRESS_BAR
 
   #if HAS_LCD_MENU
-
     if (use_click()) {
       #if BOTH(FILAMENT_LCD_DISPLAY, SDSUPPORT)
         next_filament_display = millis() + 5000UL;  // Show status message for 5s
diff --git a/Marlin/src/libs/numtostr.cpp b/Marlin/src/libs/numtostr.cpp
index 1ed315ae07..2788ec63ac 100644
--- a/Marlin/src/libs/numtostr.cpp
+++ b/Marlin/src/libs/numtostr.cpp
@@ -174,6 +174,27 @@ const char* ftostr12ns(const float &f) {
   return &conv[3];
 }
 
+// Convert unsigned float to string with 12.3 format
+const char* ftostr31ns(const float &f) {
+  const long i = ((f < 0 ? -f : f) * 100 + 5) / 10;
+  conv[3] = DIGIMOD(i, 100);
+  conv[4] = DIGIMOD(i, 10);
+  conv[5] = '.';
+  conv[6] = DIGIMOD(i, 1);
+  return &conv[3];
+}
+
+// Convert unsigned float to string with 123.4 format
+const char* ftostr41ns(const float &f) {
+  const long i = ((f < 0 ? -f : f) * 100 + 5) / 10;
+  conv[2] = DIGIMOD(i, 1000);
+  conv[3] = DIGIMOD(i, 100);
+  conv[4] = DIGIMOD(i, 10);
+  conv[5] = '.';
+  conv[6] = DIGIMOD(i, 1);
+  return &conv[2];
+}
+
 // Convert signed float to fixed-length string with 12.34 / _2.34 / -2.34 or -23.45 / 123.45 format
 const char* ftostr42_52(const float &f) {
   if (f <= -10 || f >= 100) return ftostr52(f); // -23.45 / 123.45
diff --git a/Marlin/src/libs/numtostr.h b/Marlin/src/libs/numtostr.h
index 8b6b83391f..ab340f52cd 100644
--- a/Marlin/src/libs/numtostr.h
+++ b/Marlin/src/libs/numtostr.h
@@ -58,6 +58,12 @@ const char* i16tostr4signrj(const int16_t x);
 // Convert unsigned float to string with 1.23 format
 const char* ftostr12ns(const float &x);
 
+// Convert unsigned float to string with 12.3 format
+const char* ftostr31ns(const float &x);
+
+// Convert unsigned float to string with 123.4 format
+const char* ftostr41ns(const float &x);
+
 // Convert signed float to fixed-length string with 12.34 / _2.34 / -2.34 or -23.45 / 123.45 format
 const char* ftostr42_52(const float &x);
 
diff --git a/Marlin/src/module/configuration_store.cpp b/Marlin/src/module/configuration_store.cpp
index 96ca20e8a2..77b807ec4c 100644
--- a/Marlin/src/module/configuration_store.cpp
+++ b/Marlin/src/module/configuration_store.cpp
@@ -94,6 +94,10 @@
   #include "../feature/powerloss.h"
 #endif
 
+#if ENABLED(POWER_MONITOR)
+  #include "../feature/power_monitor.h"
+#endif
+
 #include "../feature/pause.h"
 
 #if ENABLED(BACKLASH_COMPENSATION)
@@ -301,6 +305,11 @@ typedef struct SettingsDataStruct {
     user_thermistor_t user_thermistor[USER_THERMISTORS]; // M305 P0 R4700 T100000 B3950
   #endif
 
+  //
+  // Power monitor
+  //
+  uint8_t power_monitor_flags;                          // M430 I V W
+
   //
   // HAS_LCD_CONTRAST
   //
@@ -881,6 +890,19 @@ void MarlinSettings::postprocess() {
     }
     #endif
 
+    //
+    // Power monitor
+    //
+    {
+      #if HAS_POWER_MONITOR
+        const uint8_t &power_monitor_flags = power_monitor.flags;
+      #else
+        constexpr uint8_t power_monitor_flags = 0x00;
+      #endif
+      _FIELD_TEST(power_monitor_flags);
+      EEPROM_WRITE(power_monitor_flags);
+    }
+
     //
     // LCD Contrast
     //
@@ -1745,6 +1767,19 @@ void MarlinSettings::postprocess() {
       }
       #endif
 
+      //
+      // Power monitor
+      //
+      {
+        #if HAS_POWER_MONITOR
+          uint8_t &power_monitor_flags = power_monitor.flags;
+        #else
+          uint8_t power_monitor_flags;
+        #endif
+        _FIELD_TEST(power_monitor_flags);
+        EEPROM_READ(power_monitor_flags);
+      }
+
       //
       // LCD Contrast
       //
@@ -2604,6 +2639,11 @@ void MarlinSettings::reset() {
   //
   TERN_(HAS_USER_THERMISTORS, thermalManager.reset_user_thermistors());
 
+  //
+  // Power Monitor
+  //
+  TERN_(POWER_MONITOR, power_monitor.reset());
+
   //
   // LCD Contrast
   //
diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp
index 2b7452a78a..23c3d90a3c 100644
--- a/Marlin/src/module/temperature.cpp
+++ b/Marlin/src/module/temperature.cpp
@@ -84,6 +84,10 @@
   #include "../feature/filwidth.h"
 #endif
 
+#if HAS_POWER_MONITOR
+  #include "../feature/power_monitor.h"
+#endif
+
 #if ENABLED(EMERGENCY_PARSER)
   #include "../feature/e_parser.h"
 #endif
@@ -1529,11 +1533,13 @@ void Temperature::updateTemperaturesFromRawValues() {
   #if HAS_HOTEND
     HOTEND_LOOP() temp_hotend[e].celsius = analog_to_celsius_hotend(temp_hotend[e].raw, e);
   #endif
+
   TERN_(HAS_HEATED_BED, temp_bed.celsius = analog_to_celsius_bed(temp_bed.raw));
   TERN_(HAS_TEMP_CHAMBER, temp_chamber.celsius = analog_to_celsius_chamber(temp_chamber.raw));
   TERN_(HAS_TEMP_PROBE, temp_probe.celsius = analog_to_celsius_probe(temp_probe.raw));
   TERN_(TEMP_SENSOR_1_AS_REDUNDANT, redundant_temperature = analog_to_celsius_hotend(redundant_temperature_raw, 1));
   TERN_(FILAMENT_WIDTH_SENSOR, filwidth.update_measured_mm());
+  TERN_(HAS_POWER_MONITOR, power_monitor.capture_values());
 
   // Reset the watchdog on good temperature measurement
   watchdog_refresh();
@@ -1740,6 +1746,12 @@ void Temperature::init() {
   #if HAS_ADC_BUTTONS
     HAL_ANALOG_SELECT(ADC_KEYPAD_PIN);
   #endif
+  #if ENABLED(POWER_MONITOR_CURRENT)
+    HAL_ANALOG_SELECT(POWER_MONITOR_CURRENT_PIN);
+  #endif
+  #if ENABLED(POWER_MONITOR_VOLTAGE)
+    HAL_ANALOG_SELECT(POWER_MONITOR_VOLTAGE_PIN);
+  #endif
 
   HAL_timer_start(TEMP_TIMER_NUM, TEMP_TIMER_FREQUENCY);
   ENABLE_TEMPERATURE_INTERRUPT();
@@ -2760,13 +2772,31 @@ void Temperature::tick() {
     #if ENABLED(FILAMENT_WIDTH_SENSOR)
       case Prepare_FILWIDTH: HAL_START_ADC(FILWIDTH_PIN); break;
       case Measure_FILWIDTH:
-        if (!HAL_ADC_READY())
-          next_sensor_state = adc_sensor_state; // redo this state
-        else
-          filwidth.accumulate(HAL_READ_ADC());
+        if (!HAL_ADC_READY()) next_sensor_state = adc_sensor_state; // Redo this state
+        else filwidth.accumulate(HAL_READ_ADC());
       break;
     #endif
 
+    #if ENABLED(POWER_MONITOR_CURRENT)
+      case Prepare_POWER_MONITOR_CURRENT:
+        HAL_START_ADC(POWER_MONITOR_CURRENT_PIN);
+        break;
+      case Measure_POWER_MONITOR_CURRENT:
+        if (!HAL_ADC_READY()) next_sensor_state = adc_sensor_state; // Redo this state
+        else power_monitor.add_current_sample(HAL_READ_ADC());
+        break;
+    #endif
+
+    #if ENABLED(POWER_MONITOR_VOLTAGE)
+      case Prepare_POWER_MONITOR_VOLTAGE:
+        HAL_START_ADC(POWER_MONITOR_VOLTAGE_PIN);
+        break;
+      case Measure_POWER_MONITOR_VOLTAGE:
+        if (!HAL_ADC_READY()) next_sensor_state = adc_sensor_state; // Redo this state
+        else power_monitor.add_voltage_sample(HAL_READ_ADC());
+        break;
+    #endif
+
     #if HAS_JOY_ADC_X
       case PrepareJoy_X: HAL_START_ADC(JOY_X_PIN); break;
       case MeasureJoy_X: ACCUMULATE_ADC(joystick.x); break;
diff --git a/Marlin/src/module/temperature.h b/Marlin/src/module/temperature.h
index 8f875f84f3..0f95d0a726 100644
--- a/Marlin/src/module/temperature.h
+++ b/Marlin/src/module/temperature.h
@@ -148,6 +148,14 @@ enum ADCSensorState : char {
   #if ENABLED(FILAMENT_WIDTH_SENSOR)
     Prepare_FILWIDTH, Measure_FILWIDTH,
   #endif
+  #if ENABLED(POWER_MONITOR_CURRENT)
+    Prepare_POWER_MONITOR_CURRENT,
+    Measure_POWER_MONITOR_CURRENT,
+  #endif
+  #if ENABLED(POWER_MONITOR_VOLTAGE)
+    Prepare_POWER_MONITOR_VOLTAGE,
+    Measure_POWER_MONITOR_VOLTAGE,
+  #endif
   #if HAS_ADC_BUTTONS
     Prepare_ADC_KEY, Measure_ADC_KEY,
   #endif
-- 
GitLab