From 5b5aa1572b087f9643c71348afba7df7eba42b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Sat, 4 Jun 2016 20:29:01 +0100 Subject: [PATCH] Non-blocking buzzer --- Marlin/Conditionals.h | 30 +++++-- Marlin/Marlin.h | 11 +++ Marlin/Marlin_main.cpp | 41 +++++++--- Marlin/buzzer.cpp | 57 ------------- Marlin/buzzer.h | 115 ++++++++++++++++++++++++-- Marlin/circularqueue.h | 146 ++++++++++++++++++++++++++++++++++ Marlin/pins_MEGATRONICS.h | 3 +- Marlin/pins_MINIRAMBO.h | 4 +- Marlin/pins_PRINTRBOARD.h | 3 +- Marlin/pins_RAMBO.h | 6 +- Marlin/pins_RAMPS_14.h | 6 +- Marlin/pins_SANGUINOLOLU_11.h | 5 +- Marlin/speaker.h | 92 +++++++++++++++++++++ Marlin/ultralcd.cpp | 21 +---- 14 files changed, 432 insertions(+), 108 deletions(-) delete mode 100644 Marlin/buzzer.cpp create mode 100644 Marlin/circularqueue.h create mode 100644 Marlin/speaker.h diff --git a/Marlin/Conditionals.h b/Marlin/Conditionals.h index 54b111bb655a..74e0d1c3d223 100644 --- a/Marlin/Conditionals.h +++ b/Marlin/Conditionals.h @@ -43,8 +43,7 @@ #endif #ifndef CONFIGURATION_LCD // Get the LCD defines which are needed first - - #define CONFIGURATION_LCD +#define CONFIGURATION_LCD #define LCD_HAS_DIRECTIONAL_BUTTONS (BUTTON_EXISTS(UP) || BUTTON_EXISTS(DWN) || BUTTON_EXISTS(LFT) || BUTTON_EXISTS(RT)) @@ -154,11 +153,6 @@ #define ENCODER_STEPS_PER_MENU_ITEM 1 #endif - #if ENABLED(LCD_USE_I2C_BUZZER) - #define LCD_FEEDBACK_FREQUENCY_HZ 1000 - #define LCD_FEEDBACK_FREQUENCY_DURATION_MS 100 - #endif - #define ULTIPANEL #define NEWPANEL #endif @@ -806,5 +800,27 @@ #endif #endif + /** + * Buzzer/Speaker + */ + #if ENABLED(LCD_USE_I2C_BUZZER) + #ifndef LCD_FEEDBACK_FREQUENCY_HZ + #define LCD_FEEDBACK_FREQUENCY_HZ 1000 + #endif + #ifndef LCD_FEEDBACK_FREQUENCY_DURATION_MS + #define LCD_FEEDBACK_FREQUENCY_DURATION_MS 100 + #endif + #elif PIN_EXISTS(BEEPER) + #ifndef LCD_FEEDBACK_FREQUENCY_HZ + #define LCD_FEEDBACK_FREQUENCY_HZ 5000 + #endif + #ifndef LCD_FEEDBACK_FREQUENCY_DURATION_MS + #define LCD_FEEDBACK_FREQUENCY_DURATION_MS 2 + #endif + #else + #ifndef LCD_FEEDBACK_FREQUENCY_DURATION_MS + #define LCD_FEEDBACK_FREQUENCY_DURATION_MS 2 + #endif + #endif #endif //CONFIGURATION_LCD #endif //CONDITIONALS_H diff --git a/Marlin/Marlin.h b/Marlin/Marlin.h index 7b275b0cb8d7..1a3a170c7b39 100644 --- a/Marlin/Marlin.h +++ b/Marlin/Marlin.h @@ -373,4 +373,15 @@ extern uint8_t active_extruder; void calculate_volumetric_multipliers(); +// Buzzer +#if HAS_BUZZER + #if ENABLED(SPEAKER) + #include "speaker.h" + extern Speaker buzzer; + #else + #include "buzzer.h" + extern Buzzer buzzer; + #endif +#endif + #endif //MARLIN_H diff --git a/Marlin/Marlin_main.cpp b/Marlin/Marlin_main.cpp index 475dea4aa223..7142d968214d 100644 --- a/Marlin/Marlin_main.cpp +++ b/Marlin/Marlin_main.cpp @@ -59,7 +59,6 @@ #include "language.h" #include "pins_arduino.h" #include "math.h" -#include "buzzer.h" #if ENABLED(USE_WATCHDOG) #include "watchdog.h" @@ -354,6 +353,15 @@ static millis_t stepper_inactive_time = (DEFAULT_STEPPER_DEACTIVE_TIME) * 1000UL Stopwatch print_job_timer = Stopwatch(); #endif +// Buzzer +#if HAS_BUZZER + #if ENABLED(SPEAKER) + Speaker buzzer; + #else + Buzzer buzzer; + #endif +#endif + static uint8_t target_extruder; #if ENABLED(AUTO_BED_LEVELING_FEATURE) @@ -1233,7 +1241,7 @@ inline bool code_value_bool() { return code_value_byte() > 0; } #if ENABLED(TEMPERATURE_UNITS_SUPPORT) inline void set_input_temp_units(TempUnit units) { input_temp_units = units; } - + float code_value_temp_abs() { switch (input_temp_units) { case TEMPUNIT_C: @@ -5689,10 +5697,13 @@ inline void gcode_M226() { * M300: Play beep sound S P */ inline void gcode_M300() { - uint16_t beepS = code_seen('S') ? code_value_ushort() : 110; - uint32_t beepP = code_seen('P') ? code_value_ulong() : 1000; - if (beepP > 5000) beepP = 5000; // limit to 5 seconds - buzz(beepP, beepS); + uint16_t const frequency = code_seen('S') ? code_value_ushort() : 260; + uint16_t duration = code_seen('P') ? code_value_ushort() : 1000; + + // Limits the tone duration to 0-5 seconds. + NOMORE(duration, 5000); + + buzzer.tone(duration, frequency); } #endif // HAS_BUZZER @@ -6173,7 +6184,7 @@ inline void gcode_M428() { SERIAL_ERRORLNPGM(MSG_ERR_M428_TOO_FAR); LCD_ALERTMESSAGEPGM("Err: Too far!"); #if HAS_BUZZER - buzz(200, 40); + buzzer.tone(200, 40); #endif err = true; break; @@ -6190,8 +6201,8 @@ inline void gcode_M428() { report_current_position(); LCD_MESSAGEPGM(MSG_HOME_OFFSETS_APPLIED); #if HAS_BUZZER - buzz(200, 659); - buzz(200, 698); + buzzer.tone(200, 659); + buzzer.tone(200, 698); #endif } } @@ -8076,17 +8087,23 @@ void idle( bool no_stepper_sleep/*=false*/ #endif ) { - thermalManager.manage_heater(); + lcd_update(); + host_keepalive(); manage_inactivity( #if ENABLED(FILAMENTCHANGEENABLE) no_stepper_sleep #endif ); - host_keepalive(); - lcd_update(); + + thermalManager.manage_heater(); + #if ENABLED(PRINTCOUNTER) print_job_timer.tick(); #endif + + #if HAS_BUZZER + buzzer.tick(); + #endif } /** diff --git a/Marlin/buzzer.cpp b/Marlin/buzzer.cpp deleted file mode 100644 index 776ea4dfaedc..000000000000 --- a/Marlin/buzzer.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (C) 2016 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 "Marlin.h" -#if HAS_BUZZER - #include "buzzer.h" - #include "ultralcd.h" - - void buzz(long duration, uint16_t freq) { - if (freq > 0) { - #if ENABLED(LCD_USE_I2C_BUZZER) - lcd_buzz(duration, freq); - #elif PIN_EXISTS(BEEPER) // on-board buzzers have no further condition - SET_OUTPUT(BEEPER_PIN); - #if ENABLED(SPEAKER) // a speaker needs a AC ore a pulsed DC - //tone(BEEPER_PIN, freq, duration); // needs a PWMable pin - unsigned int delay = 1000000 / freq / 2; - int i = duration * freq / 1000; - while (i--) { - WRITE(BEEPER_PIN, HIGH); - delayMicroseconds(delay); - WRITE(BEEPER_PIN, LOW); - delayMicroseconds(delay); - } - #else // buzzer has its own resonator - needs a DC - WRITE(BEEPER_PIN, HIGH); - delay(duration); - WRITE(BEEPER_PIN, LOW); - #endif - #else - delay(duration); - #endif - } - else { - delay(duration); - } - } -#endif diff --git a/Marlin/buzzer.h b/Marlin/buzzer.h index ec568ecd87da..2e436259e46f 100644 --- a/Marlin/buzzer.h +++ b/Marlin/buzzer.h @@ -20,11 +20,114 @@ * */ -#ifndef BUZZER_H - #define BUZZER_H +#ifndef __BUZZER_H__ +#define __BUZZER_H__ - #if HAS_BUZZER - void buzz(long duration, uint16_t freq); - #endif +#include "fastio.h" +#include "watchdog.h" +#include "circularqueue.h" -#endif //BUZZER_H +#define TONE_QUEUE_LENGTH 4 + +/** + * @brief Tone structure + * @details Simple abstration of a tone based on a duration and a frequency. + * + */ +struct tone_t { + uint16_t duration; + uint16_t frequency; +}; + +/** + * @brief Buzzer class + */ +class Buzzer { + private: + struct state_t { + tone_t tone; + uint32_t timestamp; + } state; + + protected: + CircularQueue buffer; + + /** + * @brief Inverts the sate of a digital PIN + * @details This will invert the current state of an digital IO pin. + */ + void invert() { + WRITE(BEEPER_PIN, !READ(BEEPER_PIN)); + } + + /** + * @brief Turn off a digital PIN + * @details Alias of digitalWrite(PIN, LOW) using FastIO + */ + void off() { + WRITE(BEEPER_PIN, LOW); + } + + /** + * @brief Turn on a digital PIN + * @details Alias of digitalWrite(PIN, HIGH) using FastIO + */ + void on() { + WRITE(BEEPER_PIN, HIGH); + } + + /** + * @brief Resets the state of the class + * @details Brings the class state to a known one. + */ + void reset() { + this->off(); + this->state.timestamp = 0; + } + + public: + /** + * @brief Class constructor + */ + Buzzer() { + SET_OUTPUT(BEEPER_PIN); + this->reset(); + } + + /** + * @brief Add a tone to the queue + * @details Adds a tone_t structure to the ring buffer, will block IO if the + * queue is full waiting for one slot to get available. + * + * @param duration Duration of the tone in milliseconds + * @param frequency Frequency of the tone in hertz + */ + void tone(uint16_t const &duration, uint16_t const &frequency = 0) { + while (buffer.isFull()) { + delay(5); + this->tick(); + #if ENABLED(USE_WATCHDOG) + watchdog_reset(); + #endif + } + this->buffer.enqueue((tone_t) { duration, frequency }); + } + + /** + * @brief Loop function + * @details This function should be called at loop, it will take care of + * playing the tones in the queue. + */ + virtual void tick() { + if (!this->state.timestamp) { + if (this->buffer.isEmpty()) return; + + this->state.tone = this->buffer.dequeue(); + this->state.timestamp = millis() + this->state.tone.duration; + if (this->state.tone.frequency > 0) this->on(); + } + else if (millis() >= this->state.timestamp) this->reset(); + } +}; + +#endif diff --git a/Marlin/circularqueue.h b/Marlin/circularqueue.h new file mode 100644 index 000000000000..99efd244efd3 --- /dev/null +++ b/Marlin/circularqueue.h @@ -0,0 +1,146 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 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 . + * + */ + +#ifndef __CIRCULARQUEUE_H__ +#define __CIRCULARQUEUE_H__ + +#include + +/** + * @brief Circular Queue class + * @details Implementation of the classic ring buffer data structure + */ +template +class CircularQueue { + private: + + /** + * @brief Buffer structure + * @details This structure consolidates all the overhead required to handle + * a circular queue such as the pointers and the buffer vector. + */ + struct buffer_t { + uint8_t head; + uint8_t tail; + uint8_t size; + uint8_t length; + T queue[N]; + } buffer; + + public: + /** + * @brief Class constructor + * @details This class requires two template parameters, T defines the type + * of the items this queue will handle and N defines the maximum number of + * items that can be stored on the queue. + */ + CircularQueue() { + this->buffer.length = N; + this->buffer.size = this->buffer.head = this->buffer.tail = 0; + } + + /** + * @brief Removes and returns a item from the queue + * @details Removes the oldest item on the queue which is pointed by the + * buffer_t head variable, this item is then returned to the caller. + * @return type T item + */ + T dequeue() { + if (this->isEmpty()) return T(); + + T const item = this->buffer.queue[this->buffer.head++]; + --this->buffer.size; + + if (this->buffer.head == this->buffer.length) + this->buffer.head = 0; + + return item; + } + + /** + * @brief Adds an item to the queue + * @details Adds a item to the queue on the location pointed by the buffer_t + * tail vairable, will return false if there is no queue space available. + * + * @param item Item to be added to the queue + * @return true if the operation was successfull + */ + bool enqueue(T const &item) { + if (this->isFull()) return false; + + this->buffer.queue[this->buffer.tail++] = item; + ++this->buffer.size; + + if (this->buffer.tail == this->buffer.length) + this->buffer.tail = 0; + + return true; + } + + /** + * @brief Checks if the queue has no items + * @details Returns true if there are no items on the queue, false otherwise. + * @return true if queue is empty + */ + bool isEmpty() { + return this->buffer.size == 0; + } + + /** + * @brief Checks if the queue is full + * @details Returns true if the queue is full, false otherwise. + * @return true if queue is full + */ + bool isFull() { + return this->buffer.size == this->buffer.length; + } + + /** + * @brief Gets the queue size + * @details Returns the maximum number of items a queue can have. + * @return the queue lenght + */ + uint8_t length() { + return this->buffer.length; + } + + /** + * @brief Gets the next item from the queue without removing it + * @details Returns the next item on the queue but the item is not removed + * from the queue nor the pointers updated. + * @return the queue size + */ + uint8_t peek() { + return this->buffer.queue[this->buffer.head]; + } + + /** + * @brief Gets the number of items on the queue + * @details Returns the current number of items stored on the queue. + * @return type T item + */ + uint8_t size() { + return this->buffer.size; + } +}; + +#endif diff --git a/Marlin/pins_MEGATRONICS.h b/Marlin/pins_MEGATRONICS.h index 3cd467db86bc..b8022e642a4e 100644 --- a/Marlin/pins_MEGATRONICS.h +++ b/Marlin/pins_MEGATRONICS.h @@ -74,7 +74,8 @@ #define TEMP_1_PIN 15 // ANALOG NUMBERING #define TEMP_BED_PIN 14 // ANALOG NUMBERING -#define BEEPER_PIN 33 // AUX-4 +// AUX-4 +#define BEEPER_PIN 33 #if ENABLED(ULTRA_LCD) && ENABLED(NEWPANEL) diff --git a/Marlin/pins_MINIRAMBO.h b/Marlin/pins_MINIRAMBO.h index 1485c72bc155..29bc9dfb5333 100644 --- a/Marlin/pins_MINIRAMBO.h +++ b/Marlin/pins_MINIRAMBO.h @@ -94,7 +94,9 @@ #if ENABLED(NEWPANEL) - #define BEEPER_PIN 84 // Beeper on AUX-4 + // Beeper on AUX-4 + #define BEEPER_PIN 84 + #define LCD_PINS_RS 82 #define LCD_PINS_ENABLE 18 #define LCD_PINS_D4 19 diff --git a/Marlin/pins_PRINTRBOARD.h b/Marlin/pins_PRINTRBOARD.h index 5534589cb4f2..91455a615e4e 100644 --- a/Marlin/pins_PRINTRBOARD.h +++ b/Marlin/pins_PRINTRBOARD.h @@ -121,7 +121,8 @@ #endif // ULTRA_LCD && NEWPANEL #if ENABLED(VIKI2) || ENABLED(miniVIKI) - #define BEEPER_PIN 32 //FastIO + //FastIO + #define BEEPER_PIN 32 // Pins for DOGM SPI LCD Support #define DOGLCD_A0 42 //Non-FastIO #define DOGLCD_CS 43 //Non-FastIO diff --git a/Marlin/pins_RAMBO.h b/Marlin/pins_RAMBO.h index a42731a1ad33..0e7c7aadee1a 100644 --- a/Marlin/pins_RAMBO.h +++ b/Marlin/pins_RAMBO.h @@ -112,7 +112,8 @@ #if ENABLED(NEWPANEL) - #define BEEPER_PIN 79 // Beeper on AUX-4 + // Beeper on AUX-4 + #define BEEPER_PIN 79 #define LCD_PINS_RS 70 #define LCD_PINS_ENABLE 71 @@ -134,7 +135,8 @@ #else //!NEWPANEL - old style panel with shift register - #define BEEPER_PIN 33 // No Beeper added + // No Beeper added + #define BEEPER_PIN 33 //buttons are attached to a shift register // Not wired yet diff --git a/Marlin/pins_RAMPS_14.h b/Marlin/pins_RAMPS_14.h index 9c8caa27c259..a524289b844d 100644 --- a/Marlin/pins_RAMPS_14.h +++ b/Marlin/pins_RAMPS_14.h @@ -218,7 +218,8 @@ #else - #define BEEPER_PIN 33 // Beeper on AUX-4 + // Beeper on AUX-4 + #define BEEPER_PIN 33 // buttons are directly attached using AUX-2 #if ENABLED(REPRAPWORLD_KEYPAD) @@ -247,7 +248,8 @@ #endif #else // !NEWPANEL (Old-style panel with shift register) - #define BEEPER_PIN 33 // No Beeper added + // No Beeper added + #define BEEPER_PIN 33 // Buttons are attached to a shift register // Not wired yet diff --git a/Marlin/pins_SANGUINOLOLU_11.h b/Marlin/pins_SANGUINOLOLU_11.h index bd7722640358..9b00788c30f6 100644 --- a/Marlin/pins_SANGUINOLOLU_11.h +++ b/Marlin/pins_SANGUINOLOLU_11.h @@ -105,7 +105,10 @@ #define LCD_PINS_RS 30 //CS chip select /SS chip slave select #define LCD_PINS_ENABLE 29 //SID (MOSI) #define LCD_PINS_D4 17 //SCK (CLK) clock - #define BEEPER_PIN 27 // Pin 27 is taken by LED_PIN, but Melzi LED does nothing with Marlin so this can be used for BEEPER_PIN. You can use this pin with M42 instead of BEEPER_PIN. + // Pin 27 is taken by LED_PIN, but Melzi LED does nothing with + // Marlin so this can be used for BEEPER_PIN. You can use this pin + // with M42 instead of BEEPER_PIN. + #define BEEPER_PIN 27 #else // Sanguinololu 1.3 #define LCD_PINS_RS 4 #define LCD_PINS_ENABLE 17 diff --git a/Marlin/speaker.h b/Marlin/speaker.h new file mode 100644 index 000000000000..93aa6f736187 --- /dev/null +++ b/Marlin/speaker.h @@ -0,0 +1,92 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 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 . + * + */ + +#ifndef __SPEAKER_H__ +#define __SPEAKER_H__ + +#include "buzzer.h" + +class Speaker: public Buzzer { + private: + typedef Buzzer super; + + struct state_t { + tone_t tone; + uint16_t period; + uint16_t cycles; + } state; + + protected: + /** + * @brief Resets the state of the class + * @details Brings the class state to a known one. + */ + void reset() { + super::reset(); + this->state.period = 0; + this->state.cycles = 0; + } + + public: + /** + * @brief Class constructor + */ + Speaker() { + this->reset(); + } + + /** + * @brief Loop function + * @details This function should be called at loop, it will take care of + * playing the tones in the queue. + */ + virtual void tick() { + if (!this->state.cycles) { + if (this->buffer.isEmpty()) return; + + this->reset(); + this->state.tone = this->buffer.dequeue(); + + // Period is uint16, min frequency will be ~16Hz + this->state.period = 1000000UL / this->state.tone.frequency; + + this->state.cycles = + (this->state.tone.duration * 1000L) / this->state.period; + + this->state.period >>= 1; + this->state.cycles <<= 1; + + } + else { + uint32_t const us = micros(); + static uint32_t next = us + this->state.period; + + if (us >= next) { + --this->state.cycles; + next = us + this->state.period; + if (this->state.tone.frequency > 0) this->invert(); + } + } + } +}; + +#endif diff --git a/Marlin/ultralcd.cpp b/Marlin/ultralcd.cpp index c59bffb6b3d5..0838c7604fd0 100644 --- a/Marlin/ultralcd.cpp +++ b/Marlin/ultralcd.cpp @@ -978,8 +978,8 @@ void lcd_cooldown() { lcd_return_to_status(); //LCD_MESSAGEPGM(MSG_LEVEL_BED_DONE); #if HAS_BUZZER - buzz(200, 659); - buzz(200, 698); + buzzer.tone(200, 659); + buzzer.tone(200, 698); #endif } else { @@ -1978,25 +1978,10 @@ void lcd_quick_feedback() { next_button_update_ms = millis() + 500; #if ENABLED(LCD_USE_I2C_BUZZER) - #ifndef LCD_FEEDBACK_FREQUENCY_HZ - #define LCD_FEEDBACK_FREQUENCY_HZ 100 - #endif - #ifndef LCD_FEEDBACK_FREQUENCY_DURATION_MS - #define LCD_FEEDBACK_FREQUENCY_DURATION_MS (1000/6) - #endif lcd.buzz(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ); #elif PIN_EXISTS(BEEPER) - #ifndef LCD_FEEDBACK_FREQUENCY_HZ - #define LCD_FEEDBACK_FREQUENCY_HZ 5000 - #endif - #ifndef LCD_FEEDBACK_FREQUENCY_DURATION_MS - #define LCD_FEEDBACK_FREQUENCY_DURATION_MS 2 - #endif - buzz(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ); + buzzer.tone(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ); #else - #ifndef LCD_FEEDBACK_FREQUENCY_DURATION_MS - #define LCD_FEEDBACK_FREQUENCY_DURATION_MS 2 - #endif delay(LCD_FEEDBACK_FREQUENCY_DURATION_MS); #endif }