From d26178aeb050d5429aa3b154c5dd41b965384e4c Mon Sep 17 00:00:00 2001 From: Stefan de Bruijn Date: Mon, 21 Jun 2021 21:27:57 +0200 Subject: [PATCH] Moved most trinamic code to a common base class. --- Grbl_Esp32/src/Machines/TMC2209_4x.h | 4 +- Grbl_Esp32/src/Motors/TrinamicBase.cpp | 73 ++++++++++++++++ Grbl_Esp32/src/Motors/TrinamicBase.h | 84 +++++++++++++++++++ Grbl_Esp32/src/Motors/TrinamicDriver.cpp | 53 +----------- Grbl_Esp32/src/Motors/TrinamicDriver.h | 65 ++------------ ...DriverClass.cpp => TrinamicUartDriver.cpp} | 60 ++----------- Grbl_Esp32/src/Motors/TrinamicUartDriver.h | 76 ++++------------- 7 files changed, 191 insertions(+), 224 deletions(-) create mode 100644 Grbl_Esp32/src/Motors/TrinamicBase.cpp create mode 100644 Grbl_Esp32/src/Motors/TrinamicBase.h rename Grbl_Esp32/src/Motors/{TrinamicUartDriverClass.cpp => TrinamicUartDriver.cpp} (82%) diff --git a/Grbl_Esp32/src/Machines/TMC2209_4x.h b/Grbl_Esp32/src/Machines/TMC2209_4x.h index fd2bba86..4631fdce 100644 --- a/Grbl_Esp32/src/Machines/TMC2209_4x.h +++ b/Grbl_Esp32/src/Machines/TMC2209_4x.h @@ -31,8 +31,8 @@ #define N_AXIS 4 -#define TRINAMIC_UART_RUN_MODE TrinamicUartMode :: StealthChop -#define TRINAMIC_UART_HOMING_MODE TrinamicUartMode :: StallGuard +#define TRINAMIC_RUN_MODE TrinamicMode :: StealthChop +#define TRINAMIC_HOMING_MODE TrinamicMode :: StallGuard #define TMC_UART UART_NUM_1 #define TMC_UART_RX "gpio.21" diff --git a/Grbl_Esp32/src/Motors/TrinamicBase.cpp b/Grbl_Esp32/src/Motors/TrinamicBase.cpp new file mode 100644 index 00000000..44023f47 --- /dev/null +++ b/Grbl_Esp32/src/Motors/TrinamicBase.cpp @@ -0,0 +1,73 @@ +/* + TrinamicBase.cpp + + Part of Grbl_ESP32 + 2021 - Stefan de Bruijn + + Grbl 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. + Grbl 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 Grbl. If not, see . +*/ + + +#include "TrinamicBase.h" +#include "../MachineConfig.h" +#include + +namespace Motors { + TrinamicBase* TrinamicBase::List = NULL; // a static list of all drivers for stallguard reporting + + uint8_t TrinamicBase::get_next_index() { + static uint8_t index = 1; // they start at 1 + return index++; + } + + // Prints StallGuard data that is useful for tuning. + void TrinamicBase::readSgTask(void* pvParameters) { + auto trinamicDriver = static_cast(pvParameters); + + TickType_t xLastWakeTime; + const TickType_t xreadSg = 200; // in ticks (typically ms) + auto n_axis = config->_axes->_numberAxis; + + xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. + while (true) { // don't ever return from this or the task dies + std::atomic_thread_fence(std::memory_order::memory_order_seq_cst); // read fence for settings + if (sys.state == State::Cycle || sys.state == State::Homing || sys.state == State::Jog) { + for (TrinamicBase* p = List; p; p = p->link) { + if (p->_stallguardDebugMode) { + //info_serial("SG:%d", p->_stallguardDebugMode); + p->debug_message(); + } + } + } // sys.state + + vTaskDelayUntil(&xLastWakeTime, xreadSg); + + static UBaseType_t uxHighWaterMark = 0; +#ifdef DEBUG_TASK_STACK + reportTaskStackSize(uxHighWaterMark); +#endif + } + } + + // calculate a tstep from a rate + // tstep = TRINAMIC_FCLK / (time between 1/256 steps) + // This is used to set the stallguard window from the homing speed. + // The percent is the offset on the window + uint32_t TrinamicBase::calc_tstep(float speed, float percent) { + double tstep = speed / 60.0 * config->_axes->_axis[axis_index()]->_stepsPerMm * (256.0 / _microsteps); + tstep = TRINAMIC_FCLK / tstep * percent / 100.0; + + return static_cast(tstep); + } + + +} diff --git a/Grbl_Esp32/src/Motors/TrinamicBase.h b/Grbl_Esp32/src/Motors/TrinamicBase.h new file mode 100644 index 00000000..6fb0e440 --- /dev/null +++ b/Grbl_Esp32/src/Motors/TrinamicBase.h @@ -0,0 +1,84 @@ +#pragma once + +/* + TrinamicBase.h + + Part of Grbl_ESP32 + 2021 - Stefan de Bruijn + + Grbl 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. + Grbl 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 Grbl. If not, see . +*/ + +#include +#include "StandardStepper.h" + +#ifndef TRINAMIC_RUN_MODE +# define TRINAMIC_RUN_MODE TrinamicMode ::StealthChop +#endif + +#ifndef TRINAMIC_HOMING_MODE +# define TRINAMIC_HOMING_MODE TRINAMIC_RUN_MODE +#endif + +namespace Motors { + + enum class TrinamicMode : uint8_t { + None = 0, // not for machine defs! + StealthChop = 1, + CoolStep = 2, + StallGuard = 3, + }; + + class TrinamicBase : public StandardStepper { + protected: + uint32_t calc_tstep(float speed, float percent); + + uint16_t _driver_part_number; // example: use 2130 for TMC2130 + TrinamicMode _homing_mode; + float _r_sense; + bool _has_errors; + bool _disabled; + + float _run_current = 0.25; + float _hold_current = 0.25; + int _microsteps = 256; + int _stallguard = 0; + bool _stallguardDebugMode = false; + + TrinamicMode _mode = TrinamicMode::None; + + uint8_t get_next_index(); + + // Linked list of Trinamic driver instances, used by the + // StallGuard reporting task. + static TrinamicBase* List; + TrinamicBase* link; + static void readSgTask(void*); + + const double TRINAMIC_FCLK = 12700000.0; // Internal clock Approx (Hz) used to calculate TSTEP from homing rate + + public: + TrinamicBase(uint16_t partNumber) : StandardStepper(), _driver_part_number(partNumber), _homing_mode(TRINAMIC_HOMING_MODE) {} + + void group(Configuration::HandlerBase& handler) override { + handler.item("r_sense", _r_sense); + handler.item("run_current", _run_current); + handler.item("hold_current", _hold_current); + handler.item("microsteps", _microsteps); + handler.item("stallguard", _stallguard); + handler.item("stallguardDebugMode", _stallguardDebugMode); + + StandardStepper::group(handler); + } + }; + +} diff --git a/Grbl_Esp32/src/Motors/TrinamicDriver.cpp b/Grbl_Esp32/src/Motors/TrinamicDriver.cpp index c8d278bb..e86414ca 100644 --- a/Grbl_Esp32/src/Motors/TrinamicDriver.cpp +++ b/Grbl_Esp32/src/Motors/TrinamicDriver.cpp @@ -61,18 +61,9 @@ void TMC2130Stepper::switchCSpin(bool state) { } namespace Motors { - uint8_t TrinamicDriver::get_next_index() { -#ifdef TRINAMIC_DAISY_CHAIN - static uint8_t index = 1; // they start at 1 - return index++; -#else - return -1; -#endif - } - TrinamicDriver* TrinamicDriver::List = NULL; - TrinamicDriver::TrinamicDriver(uint16_t driver_part_number, int8_t spi_index) : - StandardStepper(), _homing_mode(TRINAMIC_HOMING_MODE), _driver_part_number(driver_part_number), _spi_index(spi_index) {} + TrinamicBase(driver_part_number), + _spi_index(spi_index) {} void TrinamicDriver::init() { _has_errors = false; @@ -325,17 +316,6 @@ o Read setting and send them to the driver. Called at init() and whenever rel // tmcstepper->GSTAT()); } - // calculate a tstep from a rate - // tstep = TRINAMIC_FCLK / (time between 1/256 steps) - // This is used to set the stallguard window from the homing speed. - // The percent is the offset on the window - uint32_t TrinamicDriver::calc_tstep(float speed, float percent) { - double tstep = speed / 60.0 * config->_axes->_axis[axis_index()]->_stepsPerMm * (256.0 / _microsteps); - tstep = TRINAMIC_FCLK / tstep * percent / 100.0; - - return static_cast(tstep); - } - // this can use the enable feature over SPI. The dedicated pin must be in the enable mode, // but that can be hardwired that way. void TrinamicDriver::set_disable(bool disable) { @@ -366,35 +346,6 @@ o Read setting and send them to the driver. Called at init() and whenever rel // This would be for individual motors, not the single pin for all motors. } - // Prints StallGuard data that is useful for tuning. - void TrinamicDriver::readSgTask(void* pvParameters) { - auto trinamicDriver = static_cast(pvParameters); - - TickType_t xLastWakeTime; - const TickType_t xreadSg = 200; // in ticks (typically ms) - auto n_axis = config->_axes->_numberAxis; - - xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. - while (true) { // don't ever return from this or the task dies - std::atomic_thread_fence(std::memory_order::memory_order_seq_cst); // read fence for settings - if (sys.state == State::Cycle || sys.state == State::Homing || sys.state == State::Jog) { - for (TrinamicDriver* p = List; p; p = p->link) { - if (p->_stallguardDebugMode) { - //info_serial("SG:%d", p->_stallguardDebugMode); - p->debug_message(); - } - } - } // sys.state - - vTaskDelayUntil(&xLastWakeTime, xreadSg); - - static UBaseType_t uxHighWaterMark = 0; -#ifdef DEBUG_TASK_STACK - reportTaskStackSize(uxHighWaterMark); -#endif - } - } - // =========== Reporting functions ======================== bool TrinamicDriver::report_open_load(TMC2130_n ::DRV_STATUS_t status) { diff --git a/Grbl_Esp32/src/Motors/TrinamicDriver.h b/Grbl_Esp32/src/Motors/TrinamicDriver.h index 639ea14c..cf51a135 100644 --- a/Grbl_Esp32/src/Motors/TrinamicDriver.h +++ b/Grbl_Esp32/src/Motors/TrinamicDriver.h @@ -20,7 +20,7 @@ */ #include "Motor.h" -#include "StandardStepper.h" +#include "TrinamicBase.h" #include // https://github.com/teemuatlut/TMCStepper @@ -36,18 +36,8 @@ const int NORMAL_THIGH = 0; const int TRINAMIC_SPI_FREQ = 100000; -const double TRINAMIC_FCLK = 12700000.0; // Internal clock Approx (Hz) used to calculate TSTEP from homing rate - // ==== defaults OK to define them in your machine definition ==== -#ifndef TRINAMIC_RUN_MODE -# define TRINAMIC_RUN_MODE TrinamicMode ::StealthChop -#endif - -#ifndef TRINAMIC_HOMING_MODE -# define TRINAMIC_HOMING_MODE TRINAMIC_RUN_MODE -#endif - #ifndef TRINAMIC_TOFF_DISABLE # define TRINAMIC_TOFF_DISABLE 0 #endif @@ -62,51 +52,22 @@ const double TRINAMIC_FCLK = 12700000.0; // Internal clock Approx (Hz) used to namespace Motors { - enum class TrinamicMode : uint8_t { - None = 0, // not for machine defs! - StealthChop = 1, - CoolStep = 2, - StallGuard = 3, - }; - - class TrinamicDriver : public StandardStepper { + class TrinamicDriver : public TrinamicBase { private: - uint32_t calc_tstep(float speed, float percent); - TMC2130Stepper* tmcstepper; // all other driver types are subclasses of this one - TrinamicMode _homing_mode; - Pin _cs_pin; // The chip select pin (can be the same for daisy chain) - uint16_t _driver_part_number; // example: use 2130 for TMC2130 - float _r_sense; + Pin _cs_pin; // The chip select pin (can be the same for daisy chain) int8_t _spi_index; - bool _has_errors; - bool _disabled; - float _run_current = 0.25; - float _hold_current = 0.25; - int _microsteps = 256; - int _stallguard = 0; - bool _stallguardDebugMode = false; - - TrinamicMode _mode = TrinamicMode::None; - bool test(); - void set_mode(bool isHoming); - void trinamic_test_response(); - void trinamic_stepper_enable(bool enable); + bool test(); + void set_mode(bool isHoming); + void trinamic_test_response(); + void trinamic_stepper_enable(bool enable); bool report_open_load(TMC2130_n ::DRV_STATUS_t status); bool report_short_to_ground(TMC2130_n ::DRV_STATUS_t status); bool report_over_temp(TMC2130_n ::DRV_STATUS_t status); bool report_short_to_ps(TMC2130_n ::DRV_STATUS_t status); - uint8_t get_next_index(); - - // Linked list of Trinamic driver instances, used by the - // StallGuard reporting task. - static TrinamicDriver* List; - TrinamicDriver* link; - static void readSgTask(void*); - protected: void config_message() override; @@ -129,17 +90,7 @@ namespace Motors { StandardStepper::validate(); } - void group(Configuration::HandlerBase& handler) override { - handler.item("spi_cs", _cs_pin); - handler.item("r_sense", _r_sense); - handler.item("run_current", _run_current); - handler.item("hold_current", _hold_current); - handler.item("microsteps", _microsteps); - handler.item("stallguard", _stallguard); - handler.item("stallguardDebugMode", _stallguardDebugMode); - - StandardStepper::group(handler); - } + void group(Configuration::HandlerBase& handler) override { TrinamicBase::group(handler); } // Name of the configurable. Must match the name registered in the cpp file. const char* name() const override { return "trinamic_spi"; } diff --git a/Grbl_Esp32/src/Motors/TrinamicUartDriverClass.cpp b/Grbl_Esp32/src/Motors/TrinamicUartDriver.cpp similarity index 82% rename from Grbl_Esp32/src/Motors/TrinamicUartDriverClass.cpp rename to Grbl_Esp32/src/Motors/TrinamicUartDriver.cpp index f415efbe..3a45ab2c 100644 --- a/Grbl_Esp32/src/Motors/TrinamicUartDriverClass.cpp +++ b/Grbl_Esp32/src/Motors/TrinamicUartDriver.cpp @@ -34,16 +34,10 @@ namespace Motors { bool TrinamicUartDriver::_uart_started = false; - TrinamicUartDriver* TrinamicUartDriver::List = NULL; // a static list of all drivers for stallguard reporting - - uint8_t TrinamicUartDriver::get_next_index() { - static uint8_t index = 1; // they start at 1 - return index++; - } /* HW Serial Constructor. */ TrinamicUartDriver::TrinamicUartDriver(uint16_t driver_part_number, uint8_t addr) : - StandardStepper(), _driver_part_number(driver_part_number), _addr(addr) {} + TrinamicBase(driver_part_number), _addr(addr) {} void TrinamicUartDriver::init() { if (!_uart_started) { @@ -208,7 +202,7 @@ namespace Motors { return; } - TrinamicUartMode newMode = isHoming ? TRINAMIC_UART_HOMING_MODE : TRINAMIC_UART_RUN_MODE; + TrinamicMode newMode = isHoming ? TRINAMIC_HOMING_MODE : TRINAMIC_RUN_MODE; if (newMode == _mode) { return; @@ -216,18 +210,18 @@ namespace Motors { _mode = newMode; switch (_mode) { - case TrinamicUartMode ::StealthChop: + case TrinamicMode ::StealthChop: //info_serial("StealthChop"); tmcstepper->en_spreadCycle(false); tmcstepper->pwm_autoscale(true); break; - case TrinamicUartMode ::CoolStep: + case TrinamicMode ::CoolStep: //info_serial("Coolstep"); // tmcstepper->en_pwm_mode(false); //TODO: check if this is present in TMC2208/09 tmcstepper->en_spreadCycle(true); tmcstepper->pwm_autoscale(false); break; - case TrinamicUartMode ::StallGuard: //TODO: check all configurations for stallguard + case TrinamicMode ::StallGuard: //TODO: check all configurations for stallguard { auto axisConfig = config->_axes->_axis[this->axis_index()]; auto homingFeedRate = (axisConfig->_homing != nullptr) ? axisConfig->_homing->_feedRate : 200; @@ -278,17 +272,6 @@ namespace Motors { // tmcstepper->GSTAT()); } - // calculate a tstep from a rate - // tstep = TRINAMIC_UART_FCLK / (time between 1/256 steps) - // This is used to set the stallguard window from the homing speed. - // The percent is the offset on the window - uint32_t TrinamicUartDriver::calc_tstep(float speed, float percent) { - double tstep = speed / 60.0 * config->_axes->_axis[axis_index()]->_stepsPerMm * (256.0 / _microsteps); - tstep = TRINAMIC_UART_FCLK / tstep * percent / 100.0; - - return static_cast(tstep); - } - // this can use the enable feature over SPI. The dedicated pin must be in the enable mode, // but that can be hardwired that way. void TrinamicUartDriver::set_disable(bool disable) { @@ -308,7 +291,7 @@ namespace Motors { if (_disabled) { tmcstepper->toff(TRINAMIC_UART_TOFF_DISABLE); } else { - if (_mode == TrinamicUartMode::StealthChop) { + if (_mode == TrinamicMode::StealthChop) { tmcstepper->toff(TRINAMIC_UART_TOFF_STEALTHCHOP); } else { tmcstepper->toff(TRINAMIC_UART_TOFF_COOLSTEP); @@ -319,33 +302,6 @@ namespace Motors { // This would be for individual motors, not the single pin for all motors. } - // Prints StallGuard data that is useful for tuning. - void TrinamicUartDriver::readSgTask(void* pvParameters) { - TickType_t xLastWakeTime; - const TickType_t xreadSg = 200; // in ticks (typically ms) - auto n_axis = config->_axes->_numberAxis; - - xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time. - while (true) { // don't ever return from this or the task dies - std::atomic_thread_fence(std::memory_order::memory_order_seq_cst); // read fence for settings - if (sys.state == State::Cycle || sys.state == State::Homing || sys.state == State::Jog) { - for (TrinamicUartDriver* p = List; p; p = p->link) { - if (p->_stallguardDebugMode) { - //info_serial("SG:%d", _stallguardDebugMode); - p->debug_message(); - } - } - } // sys.state - - vTaskDelayUntil(&xLastWakeTime, xreadSg); - - static UBaseType_t uxHighWaterMark = 0; -#ifdef DEBUG_TASK_STACK - reportTaskStackSize(uxHighWaterMark); -#endif - } - } - // =========== Reporting functions ======================== bool TrinamicUartDriver::report_open_load(TMC2208_n ::DRV_STATUS_t status) { @@ -395,7 +351,7 @@ namespace Motors { // Configuration registration namespace { - MotorFactory::InstanceBuilder registration_2008("tmc_2008"); - MotorFactory::InstanceBuilder registration_2009("tmc_2009"); + MotorFactory::InstanceBuilder registration_2208("tmc_2208"); + MotorFactory::InstanceBuilder registration_2209("tmc_2209"); } } diff --git a/Grbl_Esp32/src/Motors/TrinamicUartDriver.h b/Grbl_Esp32/src/Motors/TrinamicUartDriver.h index 441340c0..26d32337 100644 --- a/Grbl_Esp32/src/Motors/TrinamicUartDriver.h +++ b/Grbl_Esp32/src/Motors/TrinamicUartDriver.h @@ -20,7 +20,7 @@ */ #include "Motor.h" -#include "StandardStepper.h" +#include "TrinamicBase.h" #include "../Uart.h" #include // https://github.com/teemuatlut/TMCStepper @@ -28,18 +28,8 @@ const float TMC2208_RSENSE_DEFAULT = 0.11f; const float TMC2209_RSENSE_DEFAULT = 0.11f; -const double TRINAMIC_UART_FCLK = 12700000.0; // Internal clock Approx (Hz) used to calculate TSTEP from homing rate - // ==== defaults OK to define them in your machine definition ==== -#ifndef TRINAMIC_UART_RUN_MODE -# define TRINAMIC_UART_RUN_MODE TrinamicUartMode ::StealthChop -#endif - -#ifndef TRINAMIC_UART_HOMING_MODE -# define TRINAMIC_UART_HOMING_MODE TRINAMIC_UART_RUN_MODE -#endif - #ifndef TRINAMIC_UART_TOFF_DISABLE # define TRINAMIC_UART_TOFF_DISABLE 0 #endif @@ -70,51 +60,22 @@ extern Uart tmc_serial; namespace Motors { - enum class TrinamicUartMode : uint8_t { - None = 0, // not for machine defs! - StealthChop = 1, - CoolStep = 2, - StallGuard = 3, - }; - - class TrinamicUartDriver : public StandardStepper { + class TrinamicUartDriver : public TrinamicBase { private: static bool _uart_started; - uint32_t calc_tstep(float speed, float percent); + TMC2209Stepper* tmcstepper; // all other driver types are subclasses of this one - TMC2209Stepper* tmcstepper; // all other driver types are subclasses of this one - TrinamicUartMode _homing_mode; - uint16_t _driver_part_number; // example: use 2209 for TMC2209 - float _r_sense; - bool _has_errors; - bool _disabled; - - float _run_current = 0.25; - float _hold_current = 0.25; - int _microsteps = 256; - int _stallguard = 0; - bool _stallguardDebugMode = false; - - TrinamicUartMode _mode = TrinamicUartMode::None; - bool test(); - void set_mode(bool isHoming); - void trinamic_test_response(); - void trinamic_stepper_enable(bool enable); + bool test(); + void set_mode(bool isHoming); + void trinamic_test_response(); + void trinamic_stepper_enable(bool enable); bool report_open_load(TMC2208_n ::DRV_STATUS_t status); bool report_short_to_ground(TMC2208_n ::DRV_STATUS_t status); bool report_over_temp(TMC2208_n ::DRV_STATUS_t status); bool report_short_to_ps(TMC2208_n ::DRV_STATUS_t status); - uint8_t get_next_index(); - - // Linked list of Trinamic driver instances, used by the - // StallGuard reporting task. TODO: verify if this is really used/useful. - static TrinamicUartDriver* List; - TrinamicUartDriver* link; - static void readSgTask(void*); - protected: void config_message() override; @@ -137,34 +98,25 @@ namespace Motors { // Configuration handlers: void validate() const override { StandardStepper::validate(); } - void group(Configuration::HandlerBase& handler) override { - handler.item("r_sense", _r_sense); - handler.item("run_current", _run_current); - handler.item("hold_current", _hold_current); - handler.item("microsteps", _microsteps); - handler.item("stallguard", _stallguard); - handler.item("stallguardDebugMode", _stallguardDebugMode); - - StandardStepper::group(handler); - } + void group(Configuration::HandlerBase& handler) override { TrinamicBase::group(handler); } // Name of the configurable. Must match the name registered in the cpp file. const char* name() const override { return "trinamic_uart"; } }; - class TMC2008 : public TrinamicUartDriver { + class TMC2208 : public TrinamicUartDriver { public: - TMC2008() : TrinamicUartDriver(2008) {} + TMC2208() : TrinamicUartDriver(2208) {} // Name of the configurable. Must match the name registered in the cpp file. - const char* name() const override { return "tmc_2008"; } + const char* name() const override { return "tmc_2208"; } }; - class TMC2009 : public TrinamicUartDriver { + class TMC2209 : public TrinamicUartDriver { public: - TMC2009() : TrinamicUartDriver(2009) {} + TMC2209() : TrinamicUartDriver(2209) {} // Name of the configurable. Must match the name registered in the cpp file. - const char* name() const override { return "tmc_2009"; } + const char* name() const override { return "tmc_2209"; } }; }