From a090dd4985ff0114788b62f16e762f60a884e38f Mon Sep 17 00:00:00 2001 From: ellensp Date: Fri, 7 May 2021 17:31:45 +1200 Subject: [PATCH] Support a third serial port (#21784) --- Marlin/Configuration.h | 7 ++ Marlin/src/HAL/AVR/HAL.h | 7 ++ Marlin/src/HAL/AVR/MarlinSerial.cpp | 16 ++++ Marlin/src/HAL/AVR/MarlinSerial.h | 5 ++ Marlin/src/HAL/DUE/HAL.h | 10 +++ Marlin/src/HAL/DUE/MarlinSerial.cpp | 5 ++ Marlin/src/HAL/DUE/MarlinSerial.h | 5 ++ Marlin/src/HAL/DUE/MarlinSerialUSB.cpp | 3 + Marlin/src/HAL/DUE/MarlinSerialUSB.h | 4 + Marlin/src/HAL/LPC1768/HAL.h | 10 +++ Marlin/src/HAL/STM32/HAL.h | 10 +++ Marlin/src/HAL/STM32F1/HAL.h | 11 +++ Marlin/src/MarlinCore.cpp | 5 ++ Marlin/src/core/serial.cpp | 12 ++- Marlin/src/core/serial.h | 21 ++++- Marlin/src/core/serial_hook.h | 109 +++++++++++++++---------- Marlin/src/inc/Conditionals_LCD.h | 8 +- Marlin/src/inc/Conditionals_post.h | 1 + Marlin/src/inc/SanityCheck.h | 8 ++ buildroot/tests/LPC1768 | 2 +- 20 files changed, 212 insertions(+), 47 deletions(-) diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index d6a7456ccf820..e1f9a4be707a7 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -111,6 +111,13 @@ */ //#define SERIAL_PORT_2 -1 +/** + * Select a third serial port on the board to use for communication with the host. + * Currently only supported for AVR, DUE, LPC1768/9 and STM32/STM32F1 + * :[-1, 0, 1, 2, 3, 4, 5, 6, 7] + */ +//#define SERIAL_PORT_3 1 + /** * This setting determines the communication speed of the printer. * diff --git a/Marlin/src/HAL/AVR/HAL.h b/Marlin/src/HAL/AVR/HAL.h index 64e4f764dc787..e24b923ef03a6 100644 --- a/Marlin/src/HAL/AVR/HAL.h +++ b/Marlin/src/HAL/AVR/HAL.h @@ -103,6 +103,13 @@ typedef int8_t pin_t; #endif #define MYSERIAL2 customizedSerial2 #endif + + #ifdef SERIAL_PORT_3 + #if !WITHIN(SERIAL_PORT_3, -1, 3) + #error "SERIAL_PORT_3 must be from 0 to 3, or -1 for USB Serial." + #endif + #define MYSERIAL3 customizedSerial3 + #endif #endif #ifdef MMU2_SERIAL_PORT diff --git a/Marlin/src/HAL/AVR/MarlinSerial.cpp b/Marlin/src/HAL/AVR/MarlinSerial.cpp index 82cdcfbe83a2e..cd8bf5e6903b8 100644 --- a/Marlin/src/HAL/AVR/MarlinSerial.cpp +++ b/Marlin/src/HAL/AVR/MarlinSerial.cpp @@ -585,6 +585,22 @@ MSerialT1 customizedSerial1(MSerialT1::HasEmergencyParser); #endif // SERIAL_PORT_2 +#ifdef SERIAL_PORT_3 + + // Hookup ISR handlers + ISR(SERIAL_REGNAME(USART, SERIAL_PORT_3, _RX_vect)) { + MarlinSerial>::store_rxd_char(); + } + + ISR(SERIAL_REGNAME(USART, SERIAL_PORT_3, _UDRE_vect)) { + MarlinSerial>::_tx_udr_empty_irq(); + } + + template class MarlinSerial< MarlinSerialCfg >; + MSerialT3 customizedSerial3(MSerialT3::HasEmergencyParser); + +#endif // SERIAL_PORT_3 + #ifdef MMU2_SERIAL_PORT ISR(SERIAL_REGNAME(USART, MMU2_SERIAL_PORT, _RX_vect)) { diff --git a/Marlin/src/HAL/AVR/MarlinSerial.h b/Marlin/src/HAL/AVR/MarlinSerial.h index 26066d72087ed..0565c7b9db9ee 100644 --- a/Marlin/src/HAL/AVR/MarlinSerial.h +++ b/Marlin/src/HAL/AVR/MarlinSerial.h @@ -246,6 +246,11 @@ extern MSerialT2 customizedSerial2; #endif + #ifdef SERIAL_PORT_3 + typedef Serial1Class< MarlinSerial< MarlinSerialCfg > > MSerialT3; + extern MSerialT3 customizedSerial3; + #endif + #endif // !USBCON #ifdef MMU2_SERIAL_PORT diff --git a/Marlin/src/HAL/DUE/HAL.h b/Marlin/src/HAL/DUE/HAL.h index 2fb927948ce74..92e26bcf43626 100644 --- a/Marlin/src/HAL/DUE/HAL.h +++ b/Marlin/src/HAL/DUE/HAL.h @@ -68,6 +68,16 @@ extern DefaultSerial4 MSerial3; #endif #endif +#ifdef SERIAL_PORT_3 + #if SERIAL_PORT_3 == -1 || ENABLED(EMERGENCY_PARSER) + #define MYSERIAL3 customizedSerial3 + #elif WITHIN(SERIAL_PORT_3, 0, 3) + #define MYSERIAL3 MSERIAL(SERIAL_PORT_3) + #else + #error "SERIAL_PORT_3 must be from 0 to 3, or -1 for USB Serial." + #endif +#endif + #ifdef MMU2_SERIAL_PORT #if WITHIN(MMU2_SERIAL_PORT, 0, 3) #define MMU2_SERIAL MSERIAL(MMU2_SERIAL_PORT) diff --git a/Marlin/src/HAL/DUE/MarlinSerial.cpp b/Marlin/src/HAL/DUE/MarlinSerial.cpp index 29d9ab0797425..fe62ff5607d51 100644 --- a/Marlin/src/HAL/DUE/MarlinSerial.cpp +++ b/Marlin/src/HAL/DUE/MarlinSerial.cpp @@ -486,4 +486,9 @@ void MarlinSerial::flushTX() { MSerialT2 customizedSerial2(MarlinSerialCfg::EMERGENCYPARSER); #endif +#if defined(SERIAL_PORT_3) && SERIAL_PORT_3 >= 0 + template class MarlinSerial< MarlinSerialCfg >; + MSerialT3 customizedSerial3(MarlinSerialCfg::EMERGENCYPARSER); +#endif + #endif // ARDUINO_ARCH_SAM diff --git a/Marlin/src/HAL/DUE/MarlinSerial.h b/Marlin/src/HAL/DUE/MarlinSerial.h index 496e54c151de2..4a62e2834f7f4 100644 --- a/Marlin/src/HAL/DUE/MarlinSerial.h +++ b/Marlin/src/HAL/DUE/MarlinSerial.h @@ -149,3 +149,8 @@ struct MarlinSerialCfg { typedef Serial1Class< MarlinSerial< MarlinSerialCfg > > MSerialT2; extern MSerialT2 customizedSerial2; #endif + +#if defined(SERIAL_PORT_3) && SERIAL_PORT_3 >= 0 + typedef Serial1Class< MarlinSerial< MarlinSerialCfg > > MSerialT3; + extern MSerialT3 customizedSerial3; +#endif diff --git a/Marlin/src/HAL/DUE/MarlinSerialUSB.cpp b/Marlin/src/HAL/DUE/MarlinSerialUSB.cpp index fb5f0132551dd..67c597da80c42 100644 --- a/Marlin/src/HAL/DUE/MarlinSerialUSB.cpp +++ b/Marlin/src/HAL/DUE/MarlinSerialUSB.cpp @@ -134,6 +134,9 @@ size_t MarlinSerialUSB::write(const uint8_t c) { #if SERIAL_PORT_2 == -1 MSerialT2 customizedSerial2(TERN0(EMERGENCY_PARSER, true)); #endif +#if SERIAL_PORT_3 == -1 + MSerialT3 customizedSerial3(TERN0(EMERGENCY_PARSER, true)); +#endif #endif // HAS_USB_SERIAL #endif // ARDUINO_ARCH_SAM diff --git a/Marlin/src/HAL/DUE/MarlinSerialUSB.h b/Marlin/src/HAL/DUE/MarlinSerialUSB.h index d19fc60eb6e2e..6da1ef8c08f61 100644 --- a/Marlin/src/HAL/DUE/MarlinSerialUSB.h +++ b/Marlin/src/HAL/DUE/MarlinSerialUSB.h @@ -59,3 +59,7 @@ struct MarlinSerialUSB { extern MSerialT2 customizedSerial2; #endif +#if SERIAL_PORT_3 == -1 + typedef Serial1Class MSerialT3; + extern MSerialT3 customizedSerial3; +#endif diff --git a/Marlin/src/HAL/LPC1768/HAL.h b/Marlin/src/HAL/LPC1768/HAL.h index bcfa6c412fbb9..85e89339205f0 100644 --- a/Marlin/src/HAL/LPC1768/HAL.h +++ b/Marlin/src/HAL/LPC1768/HAL.h @@ -84,6 +84,16 @@ extern DefaultSerial1 USBSerial; #endif #endif +#ifdef SERIAL_PORT_3 + #if SERIAL_PORT_3 == -1 + #define MYSERIAL3 USBSerial + #elif WITHIN(SERIAL_PORT_3, 0, 3) + #define MYSERIAL3 MSERIAL(SERIAL_PORT_3) + #else + #error "SERIAL_PORT_3 must be from 0 to 3. You can also use -1 if the board supports Native USB." + #endif +#endif + #ifdef MMU2_SERIAL_PORT #if MMU2_SERIAL_PORT == -1 #define MMU2_SERIAL USBSerial diff --git a/Marlin/src/HAL/STM32/HAL.h b/Marlin/src/HAL/STM32/HAL.h index 8ac9e6bd8080b..2441c46eab0cc 100644 --- a/Marlin/src/HAL/STM32/HAL.h +++ b/Marlin/src/HAL/STM32/HAL.h @@ -68,6 +68,16 @@ #endif #endif +#ifdef SERIAL_PORT_3 + #if SERIAL_PORT_3 == -1 + #define MYSERIAL3 MSerial0 + #elif WITHIN(SERIAL_PORT_3, 1, 6) + #define MYSERIAL3 MSERIAL(SERIAL_PORT_3) + #else + #error "SERIAL_PORT_3 must be from 1 to 6. You can also use -1 if the board supports Native USB." + #endif +#endif + #ifdef MMU2_SERIAL_PORT #if MMU2_SERIAL_PORT == -1 #define MMU2_SERIAL MSerial0 diff --git a/Marlin/src/HAL/STM32F1/HAL.h b/Marlin/src/HAL/STM32F1/HAL.h index 8b306f48fa59f..b3d8dc9d0b3ec 100644 --- a/Marlin/src/HAL/STM32F1/HAL.h +++ b/Marlin/src/HAL/STM32F1/HAL.h @@ -98,6 +98,17 @@ #endif #endif +#ifdef SERIAL_PORT_3 + #if SERIAL_PORT_3 == -1 + #define MYSERIAL3 UsbSerial + #elif WITHIN(SERIAL_PORT_3, 1, NUM_UARTS) + #define MYSERIAL3 MSERIAL(SERIAL_PORT_3) + #else + #define MYSERIAL3 MSERIAL(1) // dummy port + static_assert(false, "SERIAL_PORT_3 must be from 1 to " STRINGIFY(NUM_UARTS) ". You can also use -1 if the board supports Native USB.") + #endif +#endif + #ifdef MMU2_SERIAL_PORT #if MMU2_SERIAL_PORT == -1 #define MMU2_SERIAL UsbSerial diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index 053256b7436fa..2794b806957d0 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -1075,6 +1075,11 @@ void setup() { MYSERIAL2.begin(BAUDRATE); serial_connect_timeout = millis() + 1000UL; while (!MYSERIAL2.connected() && PENDING(millis(), serial_connect_timeout)) { /*nada*/ } + #ifdef SERIAL_PORT_3 + MYSERIAL3.begin(BAUDRATE); + serial_connect_timeout = millis() + 1000UL; + while (!MYSERIAL3.connected() && PENDING(millis(), serial_connect_timeout)) { /*nada*/ } + #endif #endif SERIAL_ECHOLNPGM("start"); diff --git a/Marlin/src/core/serial.cpp b/Marlin/src/core/serial.cpp index 8af367c801d6e..28442594ce55d 100644 --- a/Marlin/src/core/serial.cpp +++ b/Marlin/src/core/serial.cpp @@ -44,6 +44,9 @@ PGMSTR(SP_X_LBL, " X:"); PGMSTR(SP_Y_LBL, " Y:"); PGMSTR(SP_Z_LBL, " Z:"); PGMST #if ENABLED(MEATPACK_ON_SERIAL_PORT_2) SerialLeafT2 mpSerial2(false, _SERIAL_LEAF_2); #endif +#if ENABLED(MEATPACK_ON_SERIAL_PORT_3) + SerialLeafT3 mpSerial3(false, _SERIAL_LEAF_3); +#endif // Step 2: For multiserial, handle the second serial port as well #if HAS_MULTI_SERIAL @@ -52,7 +55,14 @@ PGMSTR(SP_X_LBL, " X:"); PGMSTR(SP_Y_LBL, " Y:"); PGMSTR(SP_Z_LBL, " Z:"); PGMST SerialLeafT2 msSerial2(ethernet.have_telnet_client, MYSERIAL2, false); #endif - SerialOutputT multiSerial(SERIAL_LEAF_1, SERIAL_LEAF_2); + #define __S_LEAF(N) ,SERIAL_LEAF_##N + #define _S_LEAF(N) __S_LEAF(N) + + SerialOutputT multiSerial( SERIAL_LEAF_1 REPEAT_S(2, INCREMENT(NUM_SERIAL), _S_LEAF) ); + + #undef __S_LEAF + #undef _S_LEAF + #endif void serialprintPGM(PGM_P str) { diff --git a/Marlin/src/core/serial.h b/Marlin/src/core/serial.h index 2628b3d2e58e4..4565a7fc87ef9 100644 --- a/Marlin/src/core/serial.h +++ b/Marlin/src/core/serial.h @@ -95,6 +95,9 @@ extern uint8_t marlin_debug_flags; #define _SERIAL_LEAF_2 MYSERIAL2 // Don't create a useless instance here, directly use the existing instance #endif + // Nothing complicated here + #define _SERIAL_LEAF_3 MYSERIAL3 + // Hook Meatpack if it's enabled on the second leaf #if ENABLED(MEATPACK_ON_SERIAL_PORT_2) typedef MeatpackSerial SerialLeafT2; @@ -104,7 +107,23 @@ extern uint8_t marlin_debug_flags; #define SERIAL_LEAF_2 _SERIAL_LEAF_2 #endif - typedef MultiSerial SerialOutputT; + // Hook Meatpack if it's enabled on the third leaf + #if ENABLED(MEATPACK_ON_SERIAL_PORT_3) + typedef MeatpackSerial SerialLeafT3; + extern SerialLeafT3 mpSerial3; + #define SERIAL_LEAF_3 mpSerial3 + #else + #define SERIAL_LEAF_3 _SERIAL_LEAF_3 + #endif + + #define __S_MULTI(N) decltype(SERIAL_LEAF_##N), + #define _S_MULTI(N) __S_MULTI(N) + + typedef MultiSerial< REPEAT_S(1, INCREMENT(NUM_SERIAL), _S_MULTI) 0> SerialOutputT; + + #undef __S_MULTI + #undef _S_MULTI + extern SerialOutputT multiSerial; #define SERIAL_IMPL multiSerial #else diff --git a/Marlin/src/core/serial_hook.h b/Marlin/src/core/serial_hook.h index 45cdcd35ed7f2..d56cb55a66be2 100644 --- a/Marlin/src/core/serial_hook.h +++ b/Marlin/src/core/serial_hook.h @@ -195,54 +195,71 @@ struct RuntimeSerial : public SerialBase< RuntimeSerial >, public Seria RuntimeSerial(const bool e, Args... args) : BaseClassT(e), SerialT(args...), writeHook(0), eofHook(0), userPointer(0) {} }; -// A class that duplicates its output conditionally to 2 serial interfaces -template -struct MultiSerial : public SerialBase< MultiSerial > { - typedef SerialBase< MultiSerial > BaseClassT; +#define _S_CLASS(N) class Serial##N##T, +#define _S_NAME(N) Serial##N##T, + +template < REPEAT(NUM_SERIAL, _S_CLASS) const uint8_t offset=0, const uint8_t step=1 > +struct MultiSerial : public SerialBase< MultiSerial< REPEAT(NUM_SERIAL, _S_NAME) offset, step > > { + typedef SerialBase< MultiSerial< REPEAT(NUM_SERIAL, _S_NAME) offset, step > > BaseClassT; + + #undef _S_CLASS + #undef _S_NAME SerialMask portMask; - Serial0T & serial0; - Serial1T & serial1; - static constexpr uint8_t Usage = ((1 << step) - 1); // A bit mask containing as many bits as step - static constexpr uint8_t FirstOutput = (Usage << offset); - static constexpr uint8_t SecondOutput = (Usage << (offset + step)); - static constexpr uint8_t Both = FirstOutput | SecondOutput; + #define _S_DECLARE(N) Serial##N##T & serial##N; + REPEAT(NUM_SERIAL, _S_DECLARE); + #undef _S_DECLARE + + static constexpr uint8_t Usage = _BV(step) - 1; // A bit mask containing 'step' bits + + #define _OUT_PORT(N) (Usage << (offset + (step * N))), + static constexpr uint8_t output[] = { REPEAT(NUM_SERIAL, _OUT_PORT) }; + #undef _OUT_PORT + + #define _OUT_MASK(N) | output[N] + static constexpr uint8_t ALL = 0 REPEAT(NUM_SERIAL, _OUT_MASK); + #undef _OUT_MASK NO_INLINE void write(uint8_t c) { - if (portMask.enabled(FirstOutput)) serial0.write(c); - if (portMask.enabled(SecondOutput)) serial1.write(c); + #define _S_WRITE(N) if (portMask.enabled(output[N])) serial##N.write(c); + REPEAT(NUM_SERIAL, _S_WRITE); + #undef _S_WRITE } NO_INLINE void msgDone() { - if (portMask.enabled(FirstOutput)) serial0.msgDone(); - if (portMask.enabled(SecondOutput)) serial1.msgDone(); + #define _S_DONE(N) if (portMask.enabled(output[N])) serial##N.msgDone(); + REPEAT(NUM_SERIAL, _S_DONE); + #undef _S_DONE } int available(serial_index_t index) { - if (index.within(0 + offset, step + offset - 1)) - return serial0.available(index); - else if (index.within(step + offset, 2 * step + offset - 1)) - return serial1.available(index); + uint8_t pos = offset; + #define _S_AVAILABLE(N) if (index.within(pos, pos + step - 1)) return serial##N.available(index); else pos += step; + REPEAT(NUM_SERIAL, _S_AVAILABLE); + #undef _S_AVAILABLE return false; } int read(serial_index_t index) { - if (index.within(0 + offset, step + offset - 1)) - return serial0.read(index); - else if (index.within(step + offset, 2 * step + offset - 1)) - return serial1.read(index); + uint8_t pos = offset; + #define _S_READ(N) if (index.within(pos, pos + step - 1)) return serial##N.read(index); else pos += step; + REPEAT(NUM_SERIAL, _S_READ); + #undef _S_READ return -1; } void begin(const long br) { - if (portMask.enabled(FirstOutput)) serial0.begin(br); - if (portMask.enabled(SecondOutput)) serial1.begin(br); + #define _S_BEGIN(N) if (portMask.enabled(output[N])) serial##N.begin(br); + REPEAT(NUM_SERIAL, _S_BEGIN); + #undef _S_BEGIN } void end() { - if (portMask.enabled(FirstOutput)) serial0.end(); - if (portMask.enabled(SecondOutput)) serial1.end(); + #define _S_END(N) if (portMask.enabled(output[N])) serial##N.end(); + REPEAT(NUM_SERIAL, _S_END); + #undef _S_END } bool connected() { bool ret = true; - if (portMask.enabled(FirstOutput)) ret = CALL_IF_EXISTS(bool, &serial0, connected); - if (portMask.enabled(SecondOutput)) ret = ret && CALL_IF_EXISTS(bool, &serial1, connected); + #define _S_CONNECTED(N) if (portMask.enabled(output[N]) && !CALL_IF_EXISTS(bool, &serial##N, connected)) ret = false; + REPEAT(NUM_SERIAL, _S_CONNECTED); + #undef _S_CONNECTED return ret; } @@ -250,27 +267,32 @@ struct MultiSerial : public SerialBase< MultiSerial= 3 + #define Serial3Class ConditionalSerial + #endif #endif diff --git a/Marlin/src/inc/Conditionals_LCD.h b/Marlin/src/inc/Conditionals_LCD.h index d767000501cf1..afec2c2b44b41 100644 --- a/Marlin/src/inc/Conditionals_LCD.h +++ b/Marlin/src/inc/Conditionals_LCD.h @@ -955,15 +955,19 @@ // Serial Port Info // #ifdef SERIAL_PORT_2 - #define NUM_SERIAL 2 #define HAS_MULTI_SERIAL 1 + #ifdef SERIAL_PORT_3 + #define NUM_SERIAL 3 + #else + #define NUM_SERIAL 2 + #endif #elif defined(SERIAL_PORT) #define NUM_SERIAL 1 #else #define NUM_SERIAL 0 #undef BAUD_RATE_GCODE #endif -#if SERIAL_PORT == -1 || SERIAL_PORT_2 == -1 +#if SERIAL_PORT == -1 || SERIAL_PORT_2 == -1 || SERIAL_PORT_3 == -1 #define HAS_USB_SERIAL 1 #endif #if SERIAL_PORT_2 == -2 diff --git a/Marlin/src/inc/Conditionals_post.h b/Marlin/src/inc/Conditionals_post.h index d5bb3e68db920..c237e6edd9d2a 100644 --- a/Marlin/src/inc/Conditionals_post.h +++ b/Marlin/src/inc/Conditionals_post.h @@ -1859,6 +1859,7 @@ // Flag the indexed hardware serial ports in use #define CONF_SERIAL_IS(N) ( (defined(SERIAL_PORT) && SERIAL_PORT == N) \ || (defined(SERIAL_PORT_2) && SERIAL_PORT_2 == N) \ + || (defined(SERIAL_PORT_3) && SERIAL_PORT_3 == N) \ || (defined(MMU2_SERIAL_PORT) && MMU2_SERIAL_PORT == N) \ || (defined(LCD_SERIAL_PORT) && LCD_SERIAL_PORT == N) ) diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index 6959e07f12e91..2cc90e0e8bc24 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -607,6 +607,14 @@ #error "SERIAL_PORT must be defined." #elif defined(SERIAL_PORT_2) && SERIAL_PORT_2 == SERIAL_PORT #error "SERIAL_PORT_2 cannot be the same as SERIAL_PORT." +#elif defined(SERIAL_PORT_3) + #ifndef SERIAL_PORT_2 + #error "Use SERIAL_PORT_2 before using SERIAL_PORT_3" + #elif SERIAL_PORT_3 == SERIAL_PORT + #error "SERIAL_PORT_3 cannot be the same as SERIAL_PORT." + #elif SERIAL_PORT_3 == SERIAL_PORT_2 + #error "SERIAL_PORT_3 cannot be the same as SERIAL_PORT_2." + #endif #endif #if !(defined(__AVR__) && defined(USBCON)) #if ENABLED(SERIAL_XON_XOFF) && RX_BUFFER_SIZE < 1024 diff --git a/buildroot/tests/LPC1768 b/buildroot/tests/LPC1768 index 6b9f6aaac34c5..92fda54483467 100755 --- a/buildroot/tests/LPC1768 +++ b/buildroot/tests/LPC1768 @@ -14,7 +14,7 @@ set -e #exec_test $1 $2 "Default Configuration" "$3" restore_configs -opt_set MOTHERBOARD BOARD_RAMPS_14_RE_ARM_EFB NEOPIXEL_PIN P1_16 +opt_set MOTHERBOARD BOARD_RAMPS_14_RE_ARM_EFB NEOPIXEL_PIN P1_16 SERIAL_PORT_3 3 opt_enable VIKI2 SDSUPPORT SDCARD_READONLY SERIAL_PORT_2 NEOPIXEL_LED exec_test $1 $2 "ReARM EFB VIKI2, SDSUPPORT, 2 Serial ports (USB CDC + UART0), NeoPixel" "$3"