From 70998ef510a24a879f3b702dd64767d1c11d1063 Mon Sep 17 00:00:00 2001 From: AJ Quick Date: Sat, 11 Apr 2020 13:59:56 -0600 Subject: [PATCH 1/8] Add the ability to have two SX1509 under addresses 0x71 and 0x3F. --- src/DuetNG/DueXn.cpp | 76 +++++++++++++++++++++++++++++++++++----- src/DuetNG/Pins_DuetNG.h | 50 +++++++++++++++++--------- 2 files changed, 100 insertions(+), 26 deletions(-) diff --git a/src/DuetNG/DueXn.cpp b/src/DuetNG/DueXn.cpp index 4f23199fa5..0b028f7e85 100644 --- a/src/DuetNG/DueXn.cpp +++ b/src/DuetNG/DueXn.cpp @@ -24,11 +24,16 @@ namespace DuetExpansion static ExpansionBoardType dueXnBoardType = ExpansionBoardType::none; const uint8_t AdditionalIoExpanderAddress = 0x71; // address of the SX1509B we allow for general I/O expansion + const uint8_t AdditionalIoExpanderAddress2 = 0x3F; static SX1509 additionalIoExpander; static bool additionalIoExpanderPresent = false; static uint16_t additionalIoInputBits = 0; + static SX1509 additionalIoExpander2; + static bool additionalIoExpanderPresent2 = false; + static uint16_t additionalIoInputBits2 = 0; + static volatile bool taskWaiting = false; static volatile bool inputsChanged = false; @@ -178,6 +183,24 @@ namespace DuetExpansion additionalIoInputBits = additionalIoExpander.digitalReadAll(); additionalIoExpanderPresent = true; } + + bool ret2; + unsigned int attempts2 = 0; + do + { + ++attempts2; + delay(50); + ret2 = additionalIoExpander2.begin(AdditionalIoExpanderAddress2); + } while (!ret2 && attempts2 < 5); + (void)I2C_IFACE.GetErrorCounts(true); // clear the error counts in case there wasn't a device there or we didn't find it first time + + if (ret2) + { + additionalIoExpander2.pinModeMultiple((1u << 16) - 1, INPUT_PULLDOWN); + additionalIoInputBits2 = additionalIoExpander2.digitalReadAll(); + additionalIoExpanderPresent2 = true; + } + } // Return the name of the expansion board, or nullptr if no expansion board @@ -199,7 +222,19 @@ namespace DuetExpansion // Return the name of the additional expansion board, or nullptr if no expansion board const char* _ecv_array null GetAdditionalExpansionBoardName() { - return (additionalIoExpanderPresent) ? "SX1509B expander" : nullptr; + if (additionalIoExpanderPresent && additionalIoExpanderPresent2) + { + return "SX1509B expander (0x71), SX1509B expander (0x3E)"; + } + else if (additionalIoExpanderPresent) + { + return "SX1509B expander (0x71)"; + } + else if (additionalIoExpanderPresent2) + { + return "SX1509B expander (0x3E)"; + } + return nullptr; } // Set the I/O mode of a pin @@ -236,12 +271,16 @@ namespace DuetExpansion dueXnExpander.pinMode(pin, mode); } } - else if (pin >= AdditionalIoExpansionStart && pin < AdditionalIoExpansionStart + 16) + else if (pin >= AdditionalIoExpansionStart && pin < AdditionalIoExpansionStart + 32) { - if (additionalIoExpanderPresent) + if (additionalIoExpanderPresent && pin < AdditionalIoExpansionStart + 16) { additionalIoExpander.pinMode(pin - AdditionalIoExpansionStart, mode); } + else if (additionalIoExpanderPresent2 && pin >= AdditionalIoExpansionStart + 16) + { + additionalIoExpander2.pinMode(pin - (AdditionalIoExpansionStart+16), mode); + } } } @@ -262,9 +301,9 @@ namespace DuetExpansion return (dueXnInputBits & (1u << (pin - DueXnExpansionStart))) != 0; } } - else if (pin >= AdditionalIoExpansionStart && pin < AdditionalIoExpansionStart + 16) + else if (pin >= AdditionalIoExpansionStart && pin < AdditionalIoExpansionStart + 32) { - if (additionalIoExpanderPresent) + if (additionalIoExpanderPresent && pin < AdditionalIoExpansionStart + 16) { // We don't have an interrupt from the additional I/O expander, so always read fresh data. // If this is called from inside an ISR, we will get stale data. @@ -275,6 +314,17 @@ namespace DuetExpansion return (additionalIoInputBits & (1u << (pin - AdditionalIoExpansionStart))) != 0; } + else if (additionalIoExpanderPresent2 && pin >= AdditionalIoExpansionStart + 16) + { + // We don't have an interrupt from the additional I/O expander, so always read fresh data. + // If this is called from inside an ISR, we will get stale data. + if (!inInterrupt() && __get_BASEPRI() == 0) // we must not call expander.digitalRead() from within an ISR + { + additionalIoInputBits2 = additionalIoExpander2.digitalReadAll(); + } + + return (additionalIoInputBits2 & (1u << (pin - (AdditionalIoExpansionStart+16))) != 0; + } } return false; @@ -290,12 +340,16 @@ namespace DuetExpansion dueXnExpander.digitalWrite(pin - DueXnExpansionStart, high); } } - else if (pin >= AdditionalIoExpansionStart && pin < AdditionalIoExpansionStart + 16) + else if (pin >= AdditionalIoExpansionStart && pin < AdditionalIoExpansionStart + 32) { - if (additionalIoExpanderPresent) + if (additionalIoExpanderPresent && pin < AdditionalIoExpansionStart + 16) { additionalIoExpander.digitalWrite(pin - AdditionalIoExpansionStart, high); } + else if (additionalIoExpanderPresent2 && pin >= AdditionalIoExpansionStart + 16) { + } + additionalIoExpander.digitalWrite(pin - (AdditionalIoExpansionStart+16), high); + } } } @@ -309,12 +363,16 @@ namespace DuetExpansion dueXnExpander.analogWrite(pin - DueXnExpansionStart, (uint8_t)(constrain(pwm, 0.0, 1.0) * 255)); } } - else if (pin >= AdditionalIoExpansionStart && pin < AdditionalIoExpansionStart + 16) + else if (pin >= AdditionalIoExpansionStart && pin < AdditionalIoExpansionStart + 32) { - if (additionalIoExpanderPresent) + if (additionalIoExpanderPresent && pin < AdditionalIoExpansionStart + 16) { additionalIoExpander.analogWrite(pin - AdditionalIoExpansionStart, (uint8_t)(constrain(pwm, 0.0, 1.0) * 255)); } + else if (additionalIoExpanderPresent2 && pin >= AdditionalIoExpansionStart + 16) + { + additionalIoExpander2.analogWrite(pin - (AdditionalIoExpansionStart+16), (uint8_t)(constrain(pwm, 0.0, 1.0) * 255)); + } } } diff --git a/src/DuetNG/Pins_DuetNG.h b/src/DuetNG/Pins_DuetNG.h index 11d2bdfce6..9162900592 100644 --- a/src/DuetNG/Pins_DuetNG.h +++ b/src/DuetNG/Pins_DuetNG.h @@ -102,7 +102,7 @@ constexpr Pin UsbVBusPin = PortCPin(22); // Pin used to monitor VBUS on USB po #define I2C_IRQn WIRE_ISR_ID // The interrupt number it uses constexpr Pin DueXnExpansionStart = 200; // Pin numbers 200-215 are on the I/O expander -constexpr Pin AdditionalIoExpansionStart = 220; // Pin numbers 220-235 are on the additional I/O expander +constexpr Pin AdditionalIoExpansionStart = 220; // Pin numbers 220-251 are on the additional I/O expander // The numbers of entries in each array must correspond with the values of DRIVES, AXES, or HEATERS. Set values to NoPin to flag unavailability. @@ -298,22 +298,38 @@ constexpr PinEntry PinTable[] = { 210, PinCapability::rwpwm, "duex.gp2" }, { 209, PinCapability::rwpwm, "duex.gp3" }, { 208, PinCapability::rwpwm, "duex.gp4" }, - { 220, PinCapability::rwpwm, "sx1509b.0" }, - { 221, PinCapability::rwpwm, "sx1509b.1" }, - { 222, PinCapability::rwpwm, "sx1509b.2" }, - { 223, PinCapability::rwpwm, "sx1509b.3" }, - { 224, PinCapability::rwpwm, "sx1509b.4" }, - { 225, PinCapability::rwpwm, "sx1509b.5" }, - { 226, PinCapability::rwpwm, "sx1509b.6" }, - { 227, PinCapability::rwpwm, "sx1509b.7" }, - { 228, PinCapability::rwpwm, "sx1509b.8" }, - { 229, PinCapability::rwpwm, "sx1509b.9" }, - { 230, PinCapability::rwpwm, "sx1509b.10" }, - { 231, PinCapability::rwpwm, "sx1509b.11" }, - { 232, PinCapability::rwpwm, "sx1509b.12" }, - { 233, PinCapability::rwpwm, "sx1509b.13" }, - { 234, PinCapability::rwpwm, "sx1509b.14" }, - { 235, PinCapability::rwpwm, "sx1509b.15" } + { 220, PinCapability::rwpwm, "sx1509b.0,0x71.0" }, + { 221, PinCapability::rwpwm, "sx1509b.1,0x71.1" }, + { 222, PinCapability::rwpwm, "sx1509b.2,0x71.2" }, + { 223, PinCapability::rwpwm, "sx1509b.3,0x71.3" }, + { 224, PinCapability::rwpwm, "sx1509b.4,0x71.4" }, + { 225, PinCapability::rwpwm, "sx1509b.5,0x71.5" }, + { 226, PinCapability::rwpwm, "sx1509b.6,0x71.6" }, + { 227, PinCapability::rwpwm, "sx1509b.7,0x71.7" }, + { 228, PinCapability::rwpwm, "sx1509b.8,0x71.8" }, + { 229, PinCapability::rwpwm, "sx1509b.9,0x71.9" }, + { 230, PinCapability::rwpwm, "sx1509b.10,0x71.10" }, + { 231, PinCapability::rwpwm, "sx1509b.11,0x71.11" }, + { 232, PinCapability::rwpwm, "sx1509b.12,0x71.12" }, + { 233, PinCapability::rwpwm, "sx1509b.13,0x71.13" }, + { 234, PinCapability::rwpwm, "sx1509b.14,0x71.14" }, + { 235, PinCapability::rwpwm, "sx1509b.15,0x71.15" }, + { 236, PinCapability::rwpwm, "sx1509b2.0,0x3F.0" }, + { 237, PinCapability::rwpwm, "sx1509b2.1,0x3F.1" }, + { 238, PinCapability::rwpwm, "sx1509b2.2,0x3F.2" }, + { 239, PinCapability::rwpwm, "sx1509b2.3,0x3F.3" }, + { 240, PinCapability::rwpwm, "sx1509b2.4,0x3F.4" }, + { 241, PinCapability::rwpwm, "sx1509b2.5,0x3F.5" }, + { 242, PinCapability::rwpwm, "sx1509b2.6,0x3F.6" }, + { 243, PinCapability::rwpwm, "sx1509b2.7,0x3F.7" }, + { 244, PinCapability::rwpwm, "sx1509b2.8,0x3F.8" }, + { 245, PinCapability::rwpwm, "sx1509b2.9,0x3F.9" }, + { 246, PinCapability::rwpwm, "sx1509b2.10,0x3F.10" }, + { 247, PinCapability::rwpwm, "sx1509b2.11,0x3F.11" }, + { 248, PinCapability::rwpwm, "sx1509b2.12,0x3F.12" }, + { 249, PinCapability::rwpwm, "sx1509b2.13,0x3F.13" }, + { 250, PinCapability::rwpwm, "sx1509b2.14,0x3F.14" }, + { 251, PinCapability::rwpwm, "sx1509b2.15,0x3F.15" } }; constexpr unsigned int NumNamedPins = ARRAY_SIZE(PinTable); From f157ed3ff2e1b4dbb697f6d77d0da507557381c9 Mon Sep 17 00:00:00 2001 From: AJ Quick Date: Sat, 11 Apr 2020 14:14:33 -0600 Subject: [PATCH 2/8] Add the ability to have two SX1509 under addresses 0x71 and 0x3F. --- src/DuetNG/DueXn.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/DuetNG/DueXn.cpp b/src/DuetNG/DueXn.cpp index 0b028f7e85..9be4e4621f 100644 --- a/src/DuetNG/DueXn.cpp +++ b/src/DuetNG/DueXn.cpp @@ -279,7 +279,7 @@ namespace DuetExpansion } else if (additionalIoExpanderPresent2 && pin >= AdditionalIoExpansionStart + 16) { - additionalIoExpander2.pinMode(pin - (AdditionalIoExpansionStart+16), mode); + additionalIoExpander2.pinMode(pin - (AdditionalIoExpansionStart + 16), mode); } } } @@ -323,7 +323,7 @@ namespace DuetExpansion additionalIoInputBits2 = additionalIoExpander2.digitalReadAll(); } - return (additionalIoInputBits2 & (1u << (pin - (AdditionalIoExpansionStart+16))) != 0; + return (additionalIoInputBits2 & (1u << (pin - (AdditionalIoExpansionStart + 16)))) != 0; } } @@ -346,9 +346,9 @@ namespace DuetExpansion { additionalIoExpander.digitalWrite(pin - AdditionalIoExpansionStart, high); } - else if (additionalIoExpanderPresent2 && pin >= AdditionalIoExpansionStart + 16) { - } - additionalIoExpander.digitalWrite(pin - (AdditionalIoExpansionStart+16), high); + else if (additionalIoExpanderPresent2 && pin >= AdditionalIoExpansionStart + 16) + { + additionalIoExpander.digitalWrite(pin - (AdditionalIoExpansionStart + 16), high); } } } @@ -371,7 +371,7 @@ namespace DuetExpansion } else if (additionalIoExpanderPresent2 && pin >= AdditionalIoExpansionStart + 16) { - additionalIoExpander2.analogWrite(pin - (AdditionalIoExpansionStart+16), (uint8_t)(constrain(pwm, 0.0, 1.0) * 255)); + additionalIoExpander2.analogWrite(pin - (AdditionalIoExpansionStart + 16), (uint8_t)(constrain(pwm, 0.0, 1.0) * 255)); } } } From 932abb949a917947e80dd7ab4f0d9916f30def80 Mon Sep 17 00:00:00 2001 From: AJ Quick Date: Sat, 11 Apr 2020 14:58:40 -0600 Subject: [PATCH 3/8] Cannot build this version if HAS_LINUX_INTERFACE is false. Must only use the reprap.UsingLinuxInterface() if HAS_LINUX_INTERFACE is true. --- src/Platform.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Platform.cpp b/src/Platform.cpp index ad634a014c..50720869ea 100644 --- a/src/Platform.cpp +++ b/src/Platform.cpp @@ -1438,7 +1438,11 @@ void Platform::DisableAutoSave() noexcept bool Platform::IsPowerOk() const noexcept { // FIXME Implement auto-save for the SBC +#if HAS_LINUX_INTERFACE return (!autoSaveEnabled || reprap.UsingLinuxInterface()) || currentVin > autoPauseReading; +#else + return !autoSaveEnabled || currentVin > autoPauseReading; +#endif } void Platform::EnableAutoSave(float saveVoltage, float resumeVoltage) noexcept From 1c1f79acffcd035fa7cb7ac3677dc41f8ce1d503 Mon Sep 17 00:00:00 2001 From: AJ Quick Date: Sat, 11 Apr 2020 22:32:55 -0600 Subject: [PATCH 4/8] Fix bug. --- src/DuetNG/DueXn.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/DuetNG/DueXn.cpp b/src/DuetNG/DueXn.cpp index 9be4e4621f..6e42e31c9a 100644 --- a/src/DuetNG/DueXn.cpp +++ b/src/DuetNG/DueXn.cpp @@ -224,7 +224,7 @@ namespace DuetExpansion { if (additionalIoExpanderPresent && additionalIoExpanderPresent2) { - return "SX1509B expander (0x71), SX1509B expander (0x3E)"; + return "SX1509B expander (0x71 & 0x3E)"; } else if (additionalIoExpanderPresent) { @@ -233,8 +233,10 @@ namespace DuetExpansion else if (additionalIoExpanderPresent2) { return "SX1509B expander (0x3E)"; + } else + { + return nullptr; } - return nullptr; } // Set the I/O mode of a pin @@ -348,7 +350,7 @@ namespace DuetExpansion } else if (additionalIoExpanderPresent2 && pin >= AdditionalIoExpansionStart + 16) { - additionalIoExpander.digitalWrite(pin - (AdditionalIoExpansionStart + 16), high); + additionalIoExpander2.digitalWrite(pin - (AdditionalIoExpansionStart + 16), high); } } } From 96c5213f295f7543ab1d8c14ec3091350aa4c78a Mon Sep 17 00:00:00 2001 From: AJ Quick Date: Sun, 12 Apr 2020 22:04:14 -0600 Subject: [PATCH 5/8] Add missing expansion board message and add a delay after the initialization of the I2C network. (Technically not needed since it was already initialized in a previous function). --- src/DuetNG/DueXn.cpp | 1 + src/RepRap.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/DuetNG/DueXn.cpp b/src/DuetNG/DueXn.cpp index 6e42e31c9a..49afead81f 100644 --- a/src/DuetNG/DueXn.cpp +++ b/src/DuetNG/DueXn.cpp @@ -166,6 +166,7 @@ namespace DuetExpansion void AdditionalOutputInit() { I2C::Init(); // initialise I2C + delay(200); // the SX1509B has an independent power on reset, so give it some time bool ret; unsigned int attempts = 0; diff --git a/src/RepRap.cpp b/src/RepRap.cpp index 7a8645de89..301396e78d 100644 --- a/src/RepRap.cpp +++ b/src/RepRap.cpp @@ -714,6 +714,8 @@ void RepRap::Diagnostics(MessageType mtype) noexcept platform->MessageF(mtype, "%s version %s running on %s", FIRMWARE_NAME, VERSION, platform->GetElectronicsString()); const char* const expansionName = DuetExpansion::GetExpansionBoardName(); platform->MessageF(mtype, (expansionName == nullptr) ? "\n" : " + %s\n", expansionName); + const char* additionalExpansionName = DuetExpansion::GetAdditionalExpansionBoardName(); + platform->MessageF(mtype, (additionalExpansionName == nullptr) ? "\n" : " + %s\n", additionalExpansionName); #elif defined(__LPC17xx__) platform->MessageF(mtype, "%s (%s) version %s running on %s at %dMhz\n", FIRMWARE_NAME, lpcBoardName, VERSION, platform->GetElectronicsString(), (int)SystemCoreClock/1000000); #else From b8629a436b6db7406651ea2978fd3d0eb97d5f7c Mon Sep 17 00:00:00 2001 From: AJ Quick Date: Sun, 12 Apr 2020 22:25:24 -0600 Subject: [PATCH 6/8] Merge branch 'v3.01-dev' into v3.01-dev --- src/Platform.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Platform.cpp b/src/Platform.cpp index 95a6f56be4..e72864739f 100644 --- a/src/Platform.cpp +++ b/src/Platform.cpp @@ -1444,7 +1444,6 @@ bool Platform::IsPowerOk() const noexcept #endif ) || currentVin > autoPauseReading; - } void Platform::EnableAutoSave(float saveVoltage, float resumeVoltage) noexcept From b6cb25e896503261bee28914a59a8729b27f1ef2 Mon Sep 17 00:00:00 2001 From: AJ Quick Date: Tue, 14 Apr 2020 12:01:29 -0600 Subject: [PATCH 7/8] Update Pins_DuetNG.h Pins must be lowercase. --- src/DuetNG/Pins_DuetNG.h | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/DuetNG/Pins_DuetNG.h b/src/DuetNG/Pins_DuetNG.h index 9162900592..1df88a3bbb 100644 --- a/src/DuetNG/Pins_DuetNG.h +++ b/src/DuetNG/Pins_DuetNG.h @@ -314,22 +314,22 @@ constexpr PinEntry PinTable[] = { 233, PinCapability::rwpwm, "sx1509b.13,0x71.13" }, { 234, PinCapability::rwpwm, "sx1509b.14,0x71.14" }, { 235, PinCapability::rwpwm, "sx1509b.15,0x71.15" }, - { 236, PinCapability::rwpwm, "sx1509b2.0,0x3F.0" }, - { 237, PinCapability::rwpwm, "sx1509b2.1,0x3F.1" }, - { 238, PinCapability::rwpwm, "sx1509b2.2,0x3F.2" }, - { 239, PinCapability::rwpwm, "sx1509b2.3,0x3F.3" }, - { 240, PinCapability::rwpwm, "sx1509b2.4,0x3F.4" }, - { 241, PinCapability::rwpwm, "sx1509b2.5,0x3F.5" }, - { 242, PinCapability::rwpwm, "sx1509b2.6,0x3F.6" }, - { 243, PinCapability::rwpwm, "sx1509b2.7,0x3F.7" }, - { 244, PinCapability::rwpwm, "sx1509b2.8,0x3F.8" }, - { 245, PinCapability::rwpwm, "sx1509b2.9,0x3F.9" }, - { 246, PinCapability::rwpwm, "sx1509b2.10,0x3F.10" }, - { 247, PinCapability::rwpwm, "sx1509b2.11,0x3F.11" }, - { 248, PinCapability::rwpwm, "sx1509b2.12,0x3F.12" }, - { 249, PinCapability::rwpwm, "sx1509b2.13,0x3F.13" }, - { 250, PinCapability::rwpwm, "sx1509b2.14,0x3F.14" }, - { 251, PinCapability::rwpwm, "sx1509b2.15,0x3F.15" } + { 236, PinCapability::rwpwm, "sx1509b2.0,0x3f.0" }, + { 237, PinCapability::rwpwm, "sx1509b2.1,0x3f.1" }, + { 238, PinCapability::rwpwm, "sx1509b2.2,0x3f.2" }, + { 239, PinCapability::rwpwm, "sx1509b2.3,0x3f.3" }, + { 240, PinCapability::rwpwm, "sx1509b2.4,0x3f.4" }, + { 241, PinCapability::rwpwm, "sx1509b2.5,0x3f.5" }, + { 242, PinCapability::rwpwm, "sx1509b2.6,0x3f.6" }, + { 243, PinCapability::rwpwm, "sx1509b2.7,0x3f.7" }, + { 244, PinCapability::rwpwm, "sx1509b2.8,0x3f.8" }, + { 245, PinCapability::rwpwm, "sx1509b2.9,0x3f.9" }, + { 246, PinCapability::rwpwm, "sx1509b2.10,0x3f.10" }, + { 247, PinCapability::rwpwm, "sx1509b2.11,0x3f.11" }, + { 248, PinCapability::rwpwm, "sx1509b2.12,0x3f.12" }, + { 249, PinCapability::rwpwm, "sx1509b2.13,0x3f.13" }, + { 250, PinCapability::rwpwm, "sx1509b2.14,0x3f.14" }, + { 251, PinCapability::rwpwm, "sx1509b2.15,0x3f.15" } }; constexpr unsigned int NumNamedPins = ARRAY_SIZE(PinTable); From 74d13ed5c180477098fb1024ace781d1330b5c95 Mon Sep 17 00:00:00 2001 From: AJ Quick Date: Tue, 14 Apr 2020 12:02:47 -0600 Subject: [PATCH 8/8] Update DueXn.cpp Fix typo in diagnostic reporting. --- src/DuetNG/DueXn.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DuetNG/DueXn.cpp b/src/DuetNG/DueXn.cpp index 49afead81f..cb529c6bab 100644 --- a/src/DuetNG/DueXn.cpp +++ b/src/DuetNG/DueXn.cpp @@ -225,7 +225,7 @@ namespace DuetExpansion { if (additionalIoExpanderPresent && additionalIoExpanderPresent2) { - return "SX1509B expander (0x71 & 0x3E)"; + return "SX1509B expander (0x71 & 0x3F)"; } else if (additionalIoExpanderPresent) { @@ -233,7 +233,7 @@ namespace DuetExpansion } else if (additionalIoExpanderPresent2) { - return "SX1509B expander (0x3E)"; + return "SX1509B expander (0x3F)"; } else { return nullptr;