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 . + * + */ + +#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; +#endif +#if ENABLED(POWER_MONITOR_VOLTAGE) + pm_lpf_t 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 . + * + */ +#pragma once + +#include "../inc/MarlinConfig.h" + +#define PM_SAMPLE_RANGE 1024 +#define PM_K_VALUE 6 +#define PM_K_SCALE 6 + +template +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; + #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; + #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 . + * + */ + +#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 Y A (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 . + * + */ + +// +// 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