Refactor serial class with templates (#20783)

This commit is contained in:
X-Ryl669 2021-01-28 09:02:06 +01:00 committed by Scott Lahteine
parent 8da8e7d17b
commit efa1e56369
64 changed files with 782 additions and 948 deletions

View File

@ -24,6 +24,13 @@
#include "../../inc/MarlinConfig.h"
#include "HAL.h"
#ifdef USBCON
DefaultSerial MSerial(false, Serial);
#ifdef BLUETOOTH
BTSerial btSerial(false, bluetoothSerial);
#endif
#endif
// ------------------------
// Public Variables
// ------------------------

View File

@ -82,7 +82,15 @@ typedef int8_t pin_t;
// Serial ports
#ifdef USBCON
#define MYSERIAL0 TERN(BLUETOOTH, bluetoothSerial, Serial)
#include "../../core/serial_hook.h"
typedef ForwardSerial0Type< decltype(Serial) > DefaultSerial;
extern DefaultSerial MSerial;
#ifdef BLUETOOTH
typedef ForwardSerial0Type< decltype(bluetoothSerial) > BTSerial;
extern BTSerial btSerial;
#endif
#define MYSERIAL0 TERN(BLUETOOTH, btSerial, MSerial)
#else
#if !WITHIN(SERIAL_PORT, -1, 3)
#error "SERIAL_PORT must be from -1 to 3. Please update your configuration."

View File

@ -454,7 +454,7 @@ void MarlinSerial<Cfg>::flush() {
}
template<typename Cfg>
void MarlinSerial<Cfg>::write(const uint8_t c) {
size_t MarlinSerial<Cfg>::write(const uint8_t c) {
if (Cfg::TX_SIZE == 0) {
_written = true;
@ -480,7 +480,7 @@ void MarlinSerial<Cfg>::write(const uint8_t c) {
// location". This makes sure flush() won't return until the bytes
// actually got written
B_TXC = 1;
return;
return 1;
}
const uint8_t i = (tx_buffer.head + 1) & (Cfg::TX_SIZE - 1);
@ -510,6 +510,7 @@ void MarlinSerial<Cfg>::write(const uint8_t c) {
// Enable TX ISR - Non atomic, but it will eventually enable TX ISR
B_UDRIE = 1;
}
return 1;
}
template<typename Cfg>
@ -556,161 +557,6 @@ void MarlinSerial<Cfg>::flushTX() {
}
}
/**
* Imports from print.h
*/
template<typename Cfg>
void MarlinSerial<Cfg>::print(char c, int base) {
print((long)c, base);
}
template<typename Cfg>
void MarlinSerial<Cfg>::print(unsigned char b, int base) {
print((unsigned long)b, base);
}
template<typename Cfg>
void MarlinSerial<Cfg>::print(int n, int base) {
print((long)n, base);
}
template<typename Cfg>
void MarlinSerial<Cfg>::print(unsigned int n, int base) {
print((unsigned long)n, base);
}
template<typename Cfg>
void MarlinSerial<Cfg>::print(long n, int base) {
if (base == 0) write(n);
else if (base == 10) {
if (n < 0) { print('-'); n = -n; }
printNumber(n, 10);
}
else
printNumber(n, base);
}
template<typename Cfg>
void MarlinSerial<Cfg>::print(unsigned long n, int base) {
if (base == 0) write(n);
else printNumber(n, base);
}
template<typename Cfg>
void MarlinSerial<Cfg>::print(double n, int digits) {
printFloat(n, digits);
}
template<typename Cfg>
void MarlinSerial<Cfg>::println() {
print('\r');
print('\n');
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(const String& s) {
print(s);
println();
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(const char c[]) {
print(c);
println();
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(char c, int base) {
print(c, base);
println();
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(unsigned char b, int base) {
print(b, base);
println();
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(int n, int base) {
print(n, base);
println();
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(unsigned int n, int base) {
print(n, base);
println();
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(long n, int base) {
print(n, base);
println();
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(unsigned long n, int base) {
print(n, base);
println();
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(double n, int digits) {
print(n, digits);
println();
}
// Private Methods
template<typename Cfg>
void MarlinSerial<Cfg>::printNumber(unsigned long n, uint8_t base) {
if (n) {
unsigned char buf[8 * sizeof(long)]; // Enough space for base 2
int8_t i = 0;
while (n) {
buf[i++] = n % base;
n /= base;
}
while (i--)
print((char)(buf[i] + (buf[i] < 10 ? '0' : 'A' - 10)));
}
else
print('0');
}
template<typename Cfg>
void MarlinSerial<Cfg>::printFloat(double number, uint8_t digits) {
// Handle negative numbers
if (number < 0.0) {
print('-');
number = -number;
}
// Round correctly so that print(1.999, 2) prints as "2.00"
double rounding = 0.5;
LOOP_L_N(i, digits) rounding *= 0.1;
number += rounding;
// Extract the integer part of the number and print it
unsigned long int_part = (unsigned long)number;
double remainder = number - (double)int_part;
print(int_part);
// Print the decimal point, but only if there are digits beyond
if (digits) {
print('.');
// Extract digits from the remainder one at a time
while (digits--) {
remainder *= 10.0;
int toPrint = int(remainder);
print(toPrint);
remainder -= toPrint;
}
}
}
// Hookup ISR handlers
ISR(SERIAL_REGNAME(USART, SERIAL_PORT, _RX_vect)) {
MarlinSerial<MarlinSerialCfg<SERIAL_PORT>>::store_rxd_char();
@ -720,11 +566,9 @@ ISR(SERIAL_REGNAME(USART, SERIAL_PORT, _UDRE_vect)) {
MarlinSerial<MarlinSerialCfg<SERIAL_PORT>>::_tx_udr_empty_irq();
}
// Preinstantiate
template class MarlinSerial<MarlinSerialCfg<SERIAL_PORT>>;
// Instantiate
MarlinSerial<MarlinSerialCfg<SERIAL_PORT>> customizedSerial1;
// Because of the template definition above, it's required to instantiate the template to have all method generated
template class MarlinSerial< MarlinSerialCfg<SERIAL_PORT> >;
MSerialT customizedSerial1(MSerialT::HasEmergencyParser);
#ifdef SERIAL_PORT_2
@ -737,12 +581,8 @@ MarlinSerial<MarlinSerialCfg<SERIAL_PORT>> customizedSerial1;
MarlinSerial<MarlinSerialCfg<SERIAL_PORT_2>>::_tx_udr_empty_irq();
}
// Preinstantiate
template class MarlinSerial<MarlinSerialCfg<SERIAL_PORT_2>>;
// Instantiate
MarlinSerial<MarlinSerialCfg<SERIAL_PORT_2>> customizedSerial2;
template class MarlinSerial< MarlinSerialCfg<SERIAL_PORT_2> >;
MSerialT2 customizedSerial2(MSerialT2::HasEmergencyParser);
#endif
#ifdef MMU2_SERIAL_PORT
@ -755,12 +595,8 @@ MarlinSerial<MarlinSerialCfg<SERIAL_PORT>> customizedSerial1;
MarlinSerial<MMU2SerialCfg<MMU2_SERIAL_PORT>>::_tx_udr_empty_irq();
}
// Preinstantiate
template class MarlinSerial<MMU2SerialCfg<MMU2_SERIAL_PORT>>;
// Instantiate
MarlinSerial<MMU2SerialCfg<MMU2_SERIAL_PORT>> mmuSerial;
template class MarlinSerial< MarlinSerialCfg<MMU2_SERIAL_PORT> >;
MSerialT3 mmuSerial(MSerialT3::HasEmergencyParser);
#endif
#ifdef LCD_SERIAL_PORT
@ -773,11 +609,8 @@ MarlinSerial<MarlinSerialCfg<SERIAL_PORT>> customizedSerial1;
MarlinSerial<LCDSerialCfg<LCD_SERIAL_PORT>>::_tx_udr_empty_irq();
}
// Preinstantiate
template class MarlinSerial<LCDSerialCfg<LCD_SERIAL_PORT>>;
// Instantiate
MarlinSerial<LCDSerialCfg<LCD_SERIAL_PORT>> lcdSerial;
template class MarlinSerial< LCDSerialCfg<LCD_SERIAL_PORT> >;
MSerialT4 lcdSerial(MSerialT4::HasEmergencyParser);
#if HAS_DGUS_LCD
template<typename Cfg>
@ -796,7 +629,7 @@ MarlinSerial<MarlinSerialCfg<SERIAL_PORT>> customizedSerial1;
// For AT90USB targets use the UART for BT interfacing
#if defined(USBCON) && ENABLED(BLUETOOTH)
HardwareSerial bluetoothSerial;
MSerialT5 bluetoothSerial(false);
#endif
#endif // __AVR__

View File

@ -34,6 +34,7 @@
#include <WString.h>
#include "../../inc/MarlinConfigPre.h"
#include "../../core/serial_hook.h"
#ifndef SERIAL_PORT
#define SERIAL_PORT 0
@ -135,10 +136,6 @@
UART_DECL(3);
#endif
#define DEC 10
#define HEX 16
#define OCT 8
#define BIN 2
#define BYTE 0
// Templated type selector
@ -202,60 +199,30 @@
static FORCE_INLINE void atomic_set_rx_tail(ring_buffer_pos_t value);
static FORCE_INLINE ring_buffer_pos_t atomic_read_rx_tail();
public:
public:
FORCE_INLINE static void store_rxd_char();
FORCE_INLINE static void _tx_udr_empty_irq();
public:
MarlinSerial() {};
static void begin(const long);
static void end();
static int peek();
static int read();
static void flush();
static ring_buffer_pos_t available();
static void write(const uint8_t c);
static void flushTX();
#if HAS_DGUS_LCD
static ring_buffer_pos_t get_tx_buffer_free();
#endif
public:
static void begin(const long);
static void end();
static int peek();
static int read();
static void flush();
static ring_buffer_pos_t available();
static size_t write(const uint8_t c);
static void flushTX();
#if HAS_DGUS_LCD
static ring_buffer_pos_t get_tx_buffer_free();
#endif
static inline bool emergency_parser_enabled() { return Cfg::EMERGENCYPARSER; }
enum { HasEmergencyParser = Cfg::EMERGENCYPARSER };
static inline bool emergency_parser_enabled() { return Cfg::EMERGENCYPARSER; }
FORCE_INLINE static uint8_t dropped() { return Cfg::DROPPED_RX ? rx_dropped_bytes : 0; }
FORCE_INLINE static uint8_t buffer_overruns() { return Cfg::RX_OVERRUNS ? rx_buffer_overruns : 0; }
FORCE_INLINE static uint8_t framing_errors() { return Cfg::RX_FRAMING_ERRORS ? rx_framing_errors : 0; }
FORCE_INLINE static ring_buffer_pos_t rxMaxEnqueued() { return Cfg::MAX_RX_QUEUED ? rx_max_enqueued : 0; }
FORCE_INLINE static void write(const char* str) { while (*str) write(*str++); }
FORCE_INLINE static void write(const uint8_t* buffer, size_t size) { while (size--) write(*buffer++); }
FORCE_INLINE static void print(const String& s) { for (int i = 0; i < (int)s.length(); i++) write(s[i]); }
FORCE_INLINE static void print(const char* str) { write(str); }
static void print(char, int = BYTE);
static void print(unsigned char, int = BYTE);
static void print(int, int = DEC);
static void print(unsigned int, int = DEC);
static void print(long, int = DEC);
static void print(unsigned long, int = DEC);
static void print(double, int = 2);
static void println(const String& s);
static void println(const char[]);
static void println(char, int = BYTE);
static void println(unsigned char, int = BYTE);
static void println(int, int = DEC);
static void println(unsigned int, int = DEC);
static void println(long, int = DEC);
static void println(unsigned long, int = DEC);
static void println(double, int = 2);
static void println();
operator bool() { return true; }
private:
static void printNumber(unsigned long, const uint8_t);
static void printFloat(double, uint8_t);
FORCE_INLINE static uint8_t dropped() { return Cfg::DROPPED_RX ? rx_dropped_bytes : 0; }
FORCE_INLINE static uint8_t buffer_overruns() { return Cfg::RX_OVERRUNS ? rx_buffer_overruns : 0; }
FORCE_INLINE static uint8_t framing_errors() { return Cfg::RX_FRAMING_ERRORS ? rx_framing_errors : 0; }
FORCE_INLINE static ring_buffer_pos_t rxMaxEnqueued() { return Cfg::MAX_RX_QUEUED ? rx_max_enqueued : 0; }
};
template <uint8_t serial>
@ -270,12 +237,13 @@
static constexpr bool RX_FRAMING_ERRORS = ENABLED(SERIAL_STATS_RX_FRAMING_ERRORS);
static constexpr bool MAX_RX_QUEUED = ENABLED(SERIAL_STATS_MAX_RX_QUEUED);
};
extern MarlinSerial<MarlinSerialCfg<SERIAL_PORT>> customizedSerial1;
typedef Serial0Type< MarlinSerial< MarlinSerialCfg<SERIAL_PORT> > > MSerialT;
extern MSerialT customizedSerial1;
#ifdef SERIAL_PORT_2
extern MarlinSerial<MarlinSerialCfg<SERIAL_PORT_2>> customizedSerial2;
typedef Serial0Type< MarlinSerial< MarlinSerialCfg<SERIAL_PORT_2> > > MSerialT2;
extern MSerialT2 customizedSerial2;
#endif
#endif // !USBCON
@ -294,7 +262,8 @@
static constexpr bool RX_OVERRUNS = false;
};
extern MarlinSerial<MMU2SerialCfg<MMU2_SERIAL_PORT>> mmuSerial;
typedef Serial0Type< MarlinSerial< MMU2SerialCfg<MMU2_SERIAL_PORT> > > MSerialT3;
extern MSerial3 mmuSerial;
#endif
#ifdef LCD_SERIAL_PORT
@ -322,11 +291,13 @@
#endif
};
extern MarlinSerial<LCDSerialCfg<LCD_SERIAL_PORT>> lcdSerial;
typedef Serial0Type< MarlinSerial< LCDSerialCfg<LCD_SERIAL_PORT> > > MSerialT4;
extern MSerialT4 lcdSerial;
#endif
// Use the UART for Bluetooth in AT90USB configurations
#if defined(USBCON) && ENABLED(BLUETOOTH)
extern HardwareSerial bluetoothSerial;
typedef Serial0Type<HardwareSerial> MSerialT5;
extern MSerialT5 bluetoothSerial;
#endif

View File

@ -102,4 +102,11 @@ uint16_t HAL_adc_get_result() {
return HAL_adc_result;
}
// Forward the default serial port
DefaultSerial MSerial(false, Serial);
DefaultSerial1 MSerial1(false, Serial1);
DefaultSerial2 MSerial2(false, Serial2);
DefaultSerial3 MSerial3(false, Serial3);
#endif // ARDUINO_ARCH_SAM

View File

@ -36,9 +36,20 @@
#include <stdint.h>
#define _MSERIAL(X) Serial##X
#include "../../core/serial_hook.h"
typedef ForwardSerial0Type< decltype(Serial) > DefaultSerial;
extern DefaultSerial MSerial;
typedef ForwardSerial0Type< decltype(Serial1) > DefaultSerial1;
typedef ForwardSerial0Type< decltype(Serial2) > DefaultSerial2;
typedef ForwardSerial0Type< decltype(Serial3) > DefaultSerial3;
extern DefaultSerial1 MSerial1;
extern DefaultSerial2 MSerial2;
extern DefaultSerial3 MSerial3;
#define _MSERIAL(X) MSerial##X
#define MSERIAL(X) _MSERIAL(X)
#define Serial0 Serial
#define MSerial0 MSerial
// Define MYSERIAL0/1 before MarlinSerial includes!
#if SERIAL_PORT == -1 || ENABLED(EMERGENCY_PARSER)

View File

@ -382,7 +382,7 @@ void MarlinSerial<Cfg>::flush() {
}
template<typename Cfg>
void MarlinSerial<Cfg>::write(const uint8_t c) {
size_t MarlinSerial<Cfg>::write(const uint8_t c) {
_written = true;
if (Cfg::TX_SIZE == 0) {
@ -400,7 +400,7 @@ void MarlinSerial<Cfg>::write(const uint8_t c) {
// XOFF char at the RX isr, but it is properly handled there
if (!(HWUART->UART_IMR & UART_IMR_TXRDY) && (HWUART->UART_SR & UART_SR_TXRDY)) {
HWUART->UART_THR = c;
return;
return 1;
}
const uint8_t i = (tx_buffer.head + 1) & (Cfg::TX_SIZE - 1);
@ -428,6 +428,7 @@ void MarlinSerial<Cfg>::write(const uint8_t c) {
// Enable TX isr - Non atomic, but it will eventually enable TX isr
HWUART->UART_IER = UART_IER_TXRDY;
}
return 1;
}
template<typename Cfg>
@ -473,169 +474,16 @@ void MarlinSerial<Cfg>::flushTX() {
}
}
/**
* Imports from print.h
*/
template<typename Cfg>
void MarlinSerial<Cfg>::print(char c, int base) {
print((long)c, base);
}
template<typename Cfg>
void MarlinSerial<Cfg>::print(unsigned char b, int base) {
print((unsigned long)b, base);
}
template<typename Cfg>
void MarlinSerial<Cfg>::print(int n, int base) {
print((long)n, base);
}
template<typename Cfg>
void MarlinSerial<Cfg>::print(unsigned int n, int base) {
print((unsigned long)n, base);
}
template<typename Cfg>
void MarlinSerial<Cfg>::print(long n, int base) {
if (base == 0) write(n);
else if (base == 10) {
if (n < 0) { print('-'); n = -n; }
printNumber(n, 10);
}
else
printNumber(n, base);
}
template<typename Cfg>
void MarlinSerial<Cfg>::print(unsigned long n, int base) {
if (base == 0) write(n);
else printNumber(n, base);
}
template<typename Cfg>
void MarlinSerial<Cfg>::print(double n, int digits) {
printFloat(n, digits);
}
template<typename Cfg>
void MarlinSerial<Cfg>::println() {
print('\r');
print('\n');
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(const String& s) {
print(s);
println();
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(const char c[]) {
print(c);
println();
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(char c, int base) {
print(c, base);
println();
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(unsigned char b, int base) {
print(b, base);
println();
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(int n, int base) {
print(n, base);
println();
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(unsigned int n, int base) {
print(n, base);
println();
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(long n, int base) {
print(n, base);
println();
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(unsigned long n, int base) {
print(n, base);
println();
}
template<typename Cfg>
void MarlinSerial<Cfg>::println(double n, int digits) {
print(n, digits);
println();
}
// Private Methods
template<typename Cfg>
void MarlinSerial<Cfg>::printNumber(unsigned long n, uint8_t base) {
if (n) {
unsigned char buf[8 * sizeof(long)]; // Enough space for base 2
int8_t i = 0;
while (n) {
buf[i++] = n % base;
n /= base;
}
while (i--)
print((char)(buf[i] + (buf[i] < 10 ? '0' : 'A' - 10)));
}
else
print('0');
}
template<typename Cfg>
void MarlinSerial<Cfg>::printFloat(double number, uint8_t digits) {
// Handle negative numbers
if (number < 0.0) {
print('-');
number = -number;
}
// Round correctly so that print(1.999, 2) prints as "2.00"
double rounding = 0.5;
LOOP_L_N(i, digits) rounding *= 0.1;
number += rounding;
// Extract the integer part of the number and print it
unsigned long int_part = (unsigned long)number;
double remainder = number - (double)int_part;
print(int_part);
// Print the decimal point, but only if there are digits beyond
if (digits) {
print('.');
// Extract digits from the remainder one at a time
while (digits--) {
remainder *= 10.0;
int toPrint = int(remainder);
print(toPrint);
remainder -= toPrint;
}
}
}
// If not using the USB port as serial port
#if SERIAL_PORT >= 0
template class MarlinSerial<MarlinSerialCfg<SERIAL_PORT>>; // Define
MarlinSerial<MarlinSerialCfg<SERIAL_PORT>> customizedSerial1; // Instantiate
template class MarlinSerial< MarlinSerialCfg<SERIAL_PORT> >;
MSerialT customizedSerial1(MarlinSerialCfg<SERIAL_PORT>::EMERGENCYPARSER);
#endif
#if defined(SERIAL_PORT_2) && SERIAL_PORT_2 >= 0
template class MarlinSerial<MarlinSerialCfg<SERIAL_PORT_2>>; // Define
MarlinSerial<MarlinSerialCfg<SERIAL_PORT_2>> customizedSerial2; // Instantiate
template class MarlinSerial< MarlinSerialCfg<SERIAL_PORT_2> >;
MSerialT2 customizedSerial2(MarlinSerialCfg<SERIAL_PORT_2>::EMERGENCYPARSER);
#endif
#endif // ARDUINO_ARCH_SAM

View File

@ -30,11 +30,7 @@
#include <WString.h>
#include "../../inc/MarlinConfigPre.h"
#define DEC 10
#define HEX 16
#define OCT 8
#define BIN 2
#include "../../core/serial_hook.h"
// Define constants and variables for buffering incoming serial data. We're
// using a ring buffer (I think), in which rx_buffer_head is the index of the
@ -119,7 +115,7 @@ public:
static int read();
static void flush();
static ring_buffer_pos_t available();
static void write(const uint8_t c);
static size_t write(const uint8_t c);
static void flushTX();
static inline bool emergency_parser_enabled() { return Cfg::EMERGENCYPARSER; }
@ -128,35 +124,6 @@ public:
FORCE_INLINE static uint8_t buffer_overruns() { return Cfg::RX_OVERRUNS ? rx_buffer_overruns : 0; }
FORCE_INLINE static uint8_t framing_errors() { return Cfg::RX_FRAMING_ERRORS ? rx_framing_errors : 0; }
FORCE_INLINE static ring_buffer_pos_t rxMaxEnqueued() { return Cfg::MAX_RX_QUEUED ? rx_max_enqueued : 0; }
FORCE_INLINE static void write(const char* str) { while (*str) write(*str++); }
FORCE_INLINE static void write(const uint8_t* buffer, size_t size) { while (size--) write(*buffer++); }
FORCE_INLINE static void print(const String& s) { for (int i = 0; i < (int)s.length(); i++) write(s[i]); }
FORCE_INLINE static void print(const char* str) { write(str); }
static void print(char, int = 0);
static void print(unsigned char, int = 0);
static void print(int, int = DEC);
static void print(unsigned int, int = DEC);
static void print(long, int = DEC);
static void print(unsigned long, int = DEC);
static void print(double, int = 2);
static void println(const String& s);
static void println(const char[]);
static void println(char, int = 0);
static void println(unsigned char, int = 0);
static void println(int, int = DEC);
static void println(unsigned int, int = DEC);
static void println(long, int = DEC);
static void println(unsigned long, int = DEC);
static void println(double, int = 2);
static void println();
operator bool() { return true; }
private:
static void printNumber(unsigned long, const uint8_t);
static void printFloat(double, uint8_t);
};
// Serial port configuration
@ -174,9 +141,11 @@ struct MarlinSerialCfg {
};
#if SERIAL_PORT >= 0
extern MarlinSerial<MarlinSerialCfg<SERIAL_PORT>> customizedSerial1;
typedef Serial0Type< MarlinSerial< MarlinSerialCfg<SERIAL_PORT> > > MSerialT;
extern MSerialT customizedSerial1;
#endif
#if defined(SERIAL_PORT_2) && SERIAL_PORT_2 >= 0
extern MarlinSerial<MarlinSerialCfg<SERIAL_PORT_2>> customizedSerial2;
typedef Serial0Type< MarlinSerial< MarlinSerialCfg<SERIAL_PORT_2> > > MSerialT2;
extern MSerialT2 customizedSerial2;
#endif

View File

@ -50,10 +50,6 @@ extern "C" {
// Pending character
static int pending_char = -1;
#if ENABLED(EMERGENCY_PARSER)
static EmergencyParser::State emergency_state; // = EP_RESET
#endif
// Public Methods
void MarlinSerialUSB::begin(const long) {}
@ -111,13 +107,13 @@ bool MarlinSerialUSB::available() {
void MarlinSerialUSB::flush() { }
void MarlinSerialUSB::flushTX() { }
void MarlinSerialUSB::write(const uint8_t c) {
size_t MarlinSerialUSB::write(const uint8_t c) {
/* Do not even bother sending anything if USB CDC is not enumerated
or not configured on the PC side or there is no program on the PC
listening to our messages */
if (!usb_task_cdc_isenabled() || !usb_task_cdc_dtr_active())
return;
return 0;
/* Wait until the PC has read the pending to be sent data */
while (usb_task_cdc_isenabled() &&
@ -129,161 +125,20 @@ void MarlinSerialUSB::write(const uint8_t c) {
or not configured on the PC side or there is no program on the PC
listening to our messages at this point */
if (!usb_task_cdc_isenabled() || !usb_task_cdc_dtr_active())
return;
return 0;
// Fifo full
// udi_cdc_signal_overrun();
udi_cdc_putc(c);
}
/**
* Imports from print.h
*/
void MarlinSerialUSB::print(char c, int base) {
print((long)c, base);
}
void MarlinSerialUSB::print(unsigned char b, int base) {
print((unsigned long)b, base);
}
void MarlinSerialUSB::print(int n, int base) {
print((long)n, base);
}
void MarlinSerialUSB::print(unsigned int n, int base) {
print((unsigned long)n, base);
}
void MarlinSerialUSB::print(long n, int base) {
if (base == 0)
write(n);
else if (base == 10) {
if (n < 0) {
print('-');
n = -n;
}
printNumber(n, 10);
}
else
printNumber(n, base);
}
void MarlinSerialUSB::print(unsigned long n, int base) {
if (base == 0) write(n);
else printNumber(n, base);
}
void MarlinSerialUSB::print(double n, int digits) {
printFloat(n, digits);
}
void MarlinSerialUSB::println() {
print('\r');
print('\n');
}
void MarlinSerialUSB::println(const String& s) {
print(s);
println();
}
void MarlinSerialUSB::println(const char c[]) {
print(c);
println();
}
void MarlinSerialUSB::println(char c, int base) {
print(c, base);
println();
}
void MarlinSerialUSB::println(unsigned char b, int base) {
print(b, base);
println();
}
void MarlinSerialUSB::println(int n, int base) {
print(n, base);
println();
}
void MarlinSerialUSB::println(unsigned int n, int base) {
print(n, base);
println();
}
void MarlinSerialUSB::println(long n, int base) {
print(n, base);
println();
}
void MarlinSerialUSB::println(unsigned long n, int base) {
print(n, base);
println();
}
void MarlinSerialUSB::println(double n, int digits) {
print(n, digits);
println();
}
// Private Methods
void MarlinSerialUSB::printNumber(unsigned long n, uint8_t base) {
if (n) {
unsigned char buf[8 * sizeof(long)]; // Enough space for base 2
int8_t i = 0;
while (n) {
buf[i++] = n % base;
n /= base;
}
while (i--)
print((char)(buf[i] + (buf[i] < 10 ? '0' : 'A' - 10)));
}
else
print('0');
}
void MarlinSerialUSB::printFloat(double number, uint8_t digits) {
// Handle negative numbers
if (number < 0.0) {
print('-');
number = -number;
}
// Round correctly so that print(1.999, 2) prints as "2.00"
double rounding = 0.5;
LOOP_L_N(i, digits)
rounding *= 0.1;
number += rounding;
// Extract the integer part of the number and print it
unsigned long int_part = (unsigned long)number;
double remainder = number - (double)int_part;
print(int_part);
// Print the decimal point, but only if there are digits beyond
if (digits) {
print('.');
// Extract digits from the remainder one at a time
while (digits--) {
remainder *= 10.0;
int toPrint = int(remainder);
print(toPrint);
remainder -= toPrint;
}
}
return 1;
}
// Preinstantiate
#if SERIAL_PORT == -1
MarlinSerialUSB customizedSerial1;
MSerialT customizedSerial1(TERN0(EMERGENCY_PARSER, true));
#endif
#if SERIAL_PORT_2 == -1
MarlinSerialUSB customizedSerial2;
MSerialT customizedSerial2(TERN0(EMERGENCY_PARSER, true));
#endif
#endif // HAS_USB_SERIAL

View File

@ -27,20 +27,13 @@
*/
#include "../../inc/MarlinConfig.h"
#if HAS_USB_SERIAL
#include <WString.h>
#include "../../core/serial_hook.h"
#define DEC 10
#define HEX 16
#define OCT 8
#define BIN 2
class MarlinSerialUSB {
public:
MarlinSerialUSB() {};
struct MarlinSerialUSB {
static void begin(const long);
static void end();
static int peek();
@ -48,7 +41,7 @@ public:
static void flush();
static void flushTX();
static bool available();
static void write(const uint8_t c);
static size_t write(const uint8_t c);
#if ENABLED(SERIAL_STATS_DROPPED_RX)
FORCE_INLINE static uint32_t dropped() { return 0; }
@ -57,43 +50,15 @@ public:
#if ENABLED(SERIAL_STATS_MAX_RX_QUEUED)
FORCE_INLINE static int rxMaxEnqueued() { return 0; }
#endif
FORCE_INLINE static void write(const char* str) { while (*str) write(*str++); }
FORCE_INLINE static void write(const uint8_t* buffer, size_t size) { while (size--) write(*buffer++); }
FORCE_INLINE static void print(const String& s) { for (int i = 0; i < (int)s.length(); i++) write(s[i]); }
FORCE_INLINE static void print(const char* str) { write(str); }
static void print(char, int = 0);
static void print(unsigned char, int = 0);
static void print(int, int = DEC);
static void print(unsigned int, int = DEC);
static void print(long, int = DEC);
static void print(unsigned long, int = DEC);
static void print(double, int = 2);
static void println(const String& s);
static void println(const char[]);
static void println(char, int = 0);
static void println(unsigned char, int = 0);
static void println(int, int = DEC);
static void println(unsigned int, int = DEC);
static void println(long, int = DEC);
static void println(unsigned long, int = DEC);
static void println(double, int = 2);
static void println();
operator bool() { return true; }
private:
static void printNumber(unsigned long, const uint8_t);
static void printFloat(double, uint8_t);
};
typedef Serial0Type<MarlinSerialUSB> MSerialT;
#if SERIAL_PORT == -1
extern MarlinSerialUSB customizedSerial1;
extern MSerialT customizedSerial1;
#endif
#if SERIAL_PORT_2 == -1
extern MarlinSerialUSB customizedSerial2;
extern MSerialT customizedSerial2;
#endif
#endif // HAS_USB_SERIAL

View File

@ -68,7 +68,7 @@ Ctrl_status sd_mmc_spi_usb_read_10(uint32_t addr, uint16_t nb_sector) {
{
char buffer[80];
sprintf_P(buffer, PSTR("SDRD: %d @ 0x%08x\n"), nb_sector, addr);
PORT_REDIRECT(0);
PORT_REDIRECT(SERIAL_PORTMASK(0));
SERIAL_ECHO(buffer);
}
#endif
@ -108,7 +108,7 @@ Ctrl_status sd_mmc_spi_usb_write_10(uint32_t addr, uint16_t nb_sector) {
{
char buffer[80];
sprintf_P(buffer, PSTR("SDWR: %d @ 0x%08x\n"), nb_sector, addr);
PORT_REDIRECT(0);
PORT_REDIRECT(SERIAL_PORTMASK(0));
SERIAL_ECHO(buffer);
}
#endif

View File

@ -24,10 +24,7 @@
#ifdef ARDUINO_ARCH_ESP32
FlushableHardwareSerial::FlushableHardwareSerial(int uart_nr)
: HardwareSerial(uart_nr)
{}
FlushableHardwareSerial flushableSerial(0);
Serial0Type<FlushableHardwareSerial> flushableSerial(false, 0);
#endif // ARDUINO_ARCH_ESP32

View File

@ -24,14 +24,13 @@
#ifdef ARDUINO_ARCH_ESP32
#include <HardwareSerial.h>
#include "../../core/serial_hook.h"
class FlushableHardwareSerial : public HardwareSerial {
public:
FlushableHardwareSerial(int uart_nr);
inline void flushTX() { /* No need to flush the hardware serial, but defined here for compatibility. */ }
FlushableHardwareSerial(int uart_nr) : HardwareSerial(uart_nr) {}
};
extern FlushableHardwareSerial flushableSerial;
extern Serial0Type<FlushableHardwareSerial> flushableSerial;
#endif // ARDUINO_ARCH_ESP32

View File

@ -40,6 +40,10 @@
#endif
#endif
#if ENABLED(ESP3D_WIFISUPPORT)
DefaultSerial MSerial(false, Serial2Socket);
#endif
// ------------------------
// Externs
// ------------------------

View File

@ -55,7 +55,9 @@ extern portMUX_TYPE spinlock;
#if EITHER(WIFISUPPORT, ESP3D_WIFISUPPORT)
#if ENABLED(ESP3D_WIFISUPPORT)
#define MYSERIAL1 Serial2Socket
typedef ForwardSerial0Type< decltype(Serial2Socket) > DefaultSerial;
extern DefaultSerial MSerial;
#define MYSERIAL1 MSerial
#else
#define MYSERIAL1 webSocketSerial
#endif

View File

@ -29,7 +29,7 @@
#include "wifi.h"
#include <ESPAsyncWebServer.h>
WebSocketSerial webSocketSerial;
MSerialT webSocketSerial(false);
AsyncWebSocket ws("/ws"); // TODO Move inside the class.
// RingBuffer impl
@ -144,9 +144,5 @@ size_t WebSocketSerial::write(const uint8_t* buffer, size_t size) {
return written;
}
void WebSocketSerial::flushTX() {
// No need to do anything as there's no benefit to sending partial lines over the websocket connection.
}
#endif // WIFISUPPORT
#endif // ARDUINO_ARCH_ESP32

View File

@ -22,6 +22,7 @@
#pragma once
#include "../../inc/MarlinConfig.h"
#include "../../core/serial_hook.h"
#include <Stream.h>
@ -68,12 +69,9 @@ public:
int peek();
int read();
void flush();
void flushTX();
size_t write(const uint8_t c);
size_t write(const uint8_t* buffer, size_t size);
operator bool() { return true; }
#if ENABLED(SERIAL_STATS_DROPPED_RX)
FORCE_INLINE uint32_t dropped() { return 0; }
#endif
@ -83,4 +81,5 @@ public:
#endif
};
extern WebSocketSerial webSocketSerial;
typedef Serial0Type<WebSocketSerial> MSerialT;
extern MSerialT webSocketSerial;

View File

@ -24,7 +24,7 @@
#include "../../inc/MarlinConfig.h"
#include "../shared/Delay.h"
HalSerial usb_serial;
MSerialT usb_serial(TERN0(EMERGENCY_PARSER, true));
// U8glib required functions
extern "C" {

View File

@ -60,7 +60,7 @@ uint8_t _getc();
#define SHARED_SERVOS HAS_SERVOS
extern HalSerial usb_serial;
extern MSerialT usb_serial;
#define MYSERIAL0 usb_serial
#define ST7920_DELAY_1 DELAY_NS(600)

View File

@ -25,6 +25,7 @@
#if ENABLED(EMERGENCY_PARSER)
#include "../../../feature/e_parser.h"
#endif
#include "../../../core/serial_hook.h"
#include <stdarg.h>
#include <stdio.h>
@ -73,19 +74,11 @@ private:
volatile uint32_t index_read;
};
class HalSerial {
public:
#if ENABLED(EMERGENCY_PARSER)
EmergencyParser::State emergency_state;
static inline bool emergency_parser_enabled() { return true; }
#endif
struct HalSerial {
HalSerial() { host_connected = true; }
void begin(int32_t) {}
void end() {}
void end() {}
int peek() {
uint8_t value;
@ -100,7 +93,7 @@ public:
return transmit_buffer.write(c);
}
operator bool() { return host_connected; }
bool connected() { return host_connected; }
uint16_t available() {
return (uint16_t)receive_buffer.available();
@ -117,92 +110,9 @@ public:
while (transmit_buffer.available()) { /* nada */ }
}
void printf(const char *format, ...) {
static char buffer[256];
va_list vArgs;
va_start(vArgs, format);
int length = vsnprintf((char *) buffer, 256, (char const *) format, vArgs);
va_end(vArgs);
if (length > 0 && length < 256) {
if (host_connected) {
for (int i = 0; i < length;) {
if (transmit_buffer.write(buffer[i])) {
++i;
}
}
}
}
}
#define DEC 10
#define HEX 16
#define OCT 8
#define BIN 2
void print_bin(uint32_t value, uint8_t num_digits) {
uint32_t mask = 1 << (num_digits -1);
for (uint8_t i = 0; i < num_digits; i++) {
if (!(i % 4) && i) write(' ');
if (!(i % 16) && i) write(' ');
if (value & mask) write('1');
else write('0');
value <<= 1;
}
}
void print(const char value[]) { printf("%s" , value); }
void print(char value, int nbase = 0) {
if (nbase == BIN) print_bin(value, 8);
else if (nbase == OCT) printf("%3o", value);
else if (nbase == HEX) printf("%2X", value);
else if (nbase == DEC ) printf("%d", value);
else printf("%c" , value);
}
void print(unsigned char value, int nbase = 0) {
if (nbase == BIN) print_bin(value, 8);
else if (nbase == OCT) printf("%3o", value);
else if (nbase == HEX) printf("%2X", value);
else printf("%u" , value);
}
void print(int value, int nbase = 0) {
if (nbase == BIN) print_bin(value, 16);
else if (nbase == OCT) printf("%6o", value);
else if (nbase == HEX) printf("%4X", value);
else printf("%d", value);
}
void print(unsigned int value, int nbase = 0) {
if (nbase == BIN) print_bin(value, 16);
else if (nbase == OCT) printf("%6o", value);
else if (nbase == HEX) printf("%4X", value);
else printf("%u" , value);
}
void print(long value, int nbase = 0) {
if (nbase == BIN) print_bin(value, 32);
else if (nbase == OCT) printf("%11o", value);
else if (nbase == HEX) printf("%8X", value);
else printf("%ld" , value);
}
void print(unsigned long value, int nbase = 0) {
if (nbase == BIN) print_bin(value, 32);
else if (nbase == OCT) printf("%11o", value);
else if (nbase == HEX) printf("%8X", value);
else printf("%lu" , value);
}
void print(float value, int round = 6) { printf("%f" , value); }
void print(double value, int round = 6) { printf("%f" , value); }
void println(const char value[]) { printf("%s\n" , value); }
void println(char value, int nbase = 0) { print(value, nbase); println(); }
void println(unsigned char value, int nbase = 0) { print(value, nbase); println(); }
void println(int value, int nbase = 0) { print(value, nbase); println(); }
void println(unsigned int value, int nbase = 0) { print(value, nbase); println(); }
void println(long value, int nbase = 0) { print(value, nbase); println(); }
void println(unsigned long value, int nbase = 0) { print(value, nbase); println(); }
void println(float value, int round = 6) { printf("%f\n" , value); }
void println(double value, int round = 6) { printf("%f\n" , value); }
void println() { print('\n'); }
volatile RingBuffer<uint8_t, 128> receive_buffer;
volatile RingBuffer<uint8_t, 128> transmit_buffer;
volatile bool host_connected;
};
typedef Serial0Type<HalSerial> MSerialT;

View File

@ -29,6 +29,8 @@
#include "watchdog.h"
#endif
DefaultSerial USBSerial(false, UsbSerial);
uint32_t HAL_adc_reading = 0;
// U8glib required functions

View File

@ -60,12 +60,15 @@ extern "C" volatile uint32_t _millis;
#define ST7920_DELAY_3 DELAY_NS(750)
#endif
typedef ForwardSerial0Type< decltype(UsbSerial) > DefaultSerial;
extern DefaultSerial USBSerial;
#define _MSERIAL(X) MSerial##X
#define MSERIAL(X) _MSERIAL(X)
#define MSerial0 MSerial
#if SERIAL_PORT == -1
#define MYSERIAL0 UsbSerial
#define MYSERIAL0 USBSerial
#elif WITHIN(SERIAL_PORT, 0, 3)
#define MYSERIAL0 MSERIAL(SERIAL_PORT)
#else
@ -74,7 +77,7 @@ extern "C" volatile uint32_t _millis;
#ifdef SERIAL_PORT_2
#if SERIAL_PORT_2 == -1
#define MYSERIAL1 UsbSerial
#define MYSERIAL1 USBSerial
#elif WITHIN(SERIAL_PORT_2, 0, 3)
#define MYSERIAL1 MSERIAL(SERIAL_PORT_2)
#else
@ -84,7 +87,7 @@ extern "C" volatile uint32_t _millis;
#ifdef MMU2_SERIAL_PORT
#if MMU2_SERIAL_PORT == -1
#define MMU2_SERIAL UsbSerial
#define MMU2_SERIAL USBSerial
#elif WITHIN(MMU2_SERIAL_PORT, 0, 3)
#define MMU2_SERIAL MSERIAL(MMU2_SERIAL_PORT)
#else
@ -94,7 +97,7 @@ extern "C" volatile uint32_t _millis;
#ifdef LCD_SERIAL_PORT
#if LCD_SERIAL_PORT == -1
#define LCD_SERIAL UsbSerial
#define LCD_SERIAL USBSerial
#elif WITHIN(LCD_SERIAL_PORT, 0, 3)
#define LCD_SERIAL MSERIAL(LCD_SERIAL_PORT)
#else

View File

@ -25,19 +25,19 @@
#include "MarlinSerial.h"
#if USING_SERIAL_0
MarlinSerial MSerial(LPC_UART0);
MSerialT MSerial(true, LPC_UART0);
extern "C" void UART0_IRQHandler() { MSerial.IRQHandler(); }
#endif
#if USING_SERIAL_1
MarlinSerial MSerial1((LPC_UART_TypeDef *) LPC_UART1);
MSerialT MSerial1(true, (LPC_UART_TypeDef *) LPC_UART1);
extern "C" void UART1_IRQHandler() { MSerial1.IRQHandler(); }
#endif
#if USING_SERIAL_2
MarlinSerial MSerial2(LPC_UART2);
MSerialT MSerial2(true, LPC_UART2);
extern "C" void UART2_IRQHandler() { MSerial2.IRQHandler(); }
#endif
#if USING_SERIAL_3
MarlinSerial MSerial3(LPC_UART3);
MSerialT MSerial3(true, LPC_UART3);
extern "C" void UART3_IRQHandler() { MSerial3.IRQHandler(); }
#endif

View File

@ -28,6 +28,7 @@
#if ENABLED(EMERGENCY_PARSER)
#include "../../feature/e_parser.h"
#endif
#include "../../core/serial_hook.h"
#ifndef SERIAL_PORT
#define SERIAL_PORT 0
@ -41,27 +42,20 @@
class MarlinSerial : public HardwareSerial<RX_BUFFER_SIZE, TX_BUFFER_SIZE> {
public:
MarlinSerial(LPC_UART_TypeDef *UARTx) :
HardwareSerial<RX_BUFFER_SIZE, TX_BUFFER_SIZE>(UARTx)
#if ENABLED(EMERGENCY_PARSER)
, emergency_state(EmergencyParser::State::EP_RESET)
#endif
{ }
MarlinSerial(LPC_UART_TypeDef *UARTx) : HardwareSerial<RX_BUFFER_SIZE, TX_BUFFER_SIZE>(UARTx) { }
void end() {}
#if ENABLED(EMERGENCY_PARSER)
bool recv_callback(const char c) override {
emergency_parser.update(emergency_state, c);
emergency_parser.update(static_cast<Serial0Type<MarlinSerial> *>(this)->emergency_state, c);
return true; // do not discard character
}
EmergencyParser::State emergency_state;
static inline bool emergency_parser_enabled() { return true; }
#endif
};
extern MarlinSerial MSerial;
extern MarlinSerial MSerial1;
extern MarlinSerial MSerial2;
extern MarlinSerial MSerial3;
typedef Serial0Type<MarlinSerial> MSerialT;
extern MSerialT MSerial;
extern MSerialT MSerial1;
extern MSerialT MSerial2;
extern MSerialT MSerial3;

View File

@ -24,6 +24,11 @@
#include <Adafruit_ZeroDMA.h>
#include <wiring_private.h>
#ifdef ADAFRUIT_GRAND_CENTRAL_M4
DefaultSerial MSerial(false, Serial);
DefaultSerial1 MSerial1(false, Serial1);
#endif
// ------------------------
// Local defines
// ------------------------

View File

@ -32,15 +32,19 @@
#include "MarlinSerial_AGCM4.h"
// Serial ports
typedef ForwardSerial0Type< decltype(Serial) > DefaultSerial;
extern DefaultSerial MSerial;
typedef ForwardSerial0Type< decltype(Serial1) > DefaultSerial1;
extern DefaultSerial1 MSerial1;
// MYSERIAL0 required before MarlinSerial includes!
#define __MSERIAL(X) Serial##X
#define __MSERIAL(X) MSerial##X
#define _MSERIAL(X) __MSERIAL(X)
#define MSERIAL(X) _MSERIAL(INCREMENT(X))
#if SERIAL_PORT == -1
#define MYSERIAL0 Serial
#define MYSERIAL0 MSerial
#elif WITHIN(SERIAL_PORT, 0, 3)
#define MYSERIAL0 MSERIAL(SERIAL_PORT)
#else
@ -49,7 +53,7 @@
#ifdef SERIAL_PORT_2
#if SERIAL_PORT_2 == -1
#define MYSERIAL1 Serial
#define MYSERIAL1 MSerial
#elif WITHIN(SERIAL_PORT_2, 0, 3)
#define MYSERIAL1 MSERIAL(SERIAL_PORT_2)
#else
@ -59,7 +63,7 @@
#ifdef MMU2_SERIAL_PORT
#if MMU2_SERIAL_PORT == -1
#define MMU2_SERIAL Serial
#define MMU2_SERIAL MSerial
#elif WITHIN(MMU2_SERIAL_PORT, 0, 3)
#define MMU2_SERIAL MSERIAL(MMU2_SERIAL_PORT)
#else
@ -69,7 +73,7 @@
#ifdef LCD_SERIAL_PORT
#if LCD_SERIAL_PORT == -1
#define LCD_SERIAL Serial
#define LCD_SERIAL MSerial
#elif WITHIN(LCD_SERIAL_PORT, 0, 3)
#define LCD_SERIAL MSERIAL(LCD_SERIAL_PORT)
#else

View File

@ -28,7 +28,7 @@
#include "../../inc/MarlinConfig.h"
#if USING_SERIAL_1
Uart Serial2(&sercom4, PIN_SERIAL2_RX, PIN_SERIAL2_TX, PAD_SERIAL2_RX, PAD_SERIAL2_TX);
UartT Serial2(false, &sercom4, PIN_SERIAL2_RX, PIN_SERIAL2_TX, PAD_SERIAL2_RX, PAD_SERIAL2_TX);
void SERCOM4_0_Handler() { Serial2.IrqHandler(); }
void SERCOM4_1_Handler() { Serial2.IrqHandler(); }
void SERCOM4_2_Handler() { Serial2.IrqHandler(); }
@ -36,7 +36,7 @@
#endif
#if USING_SERIAL_2
Uart Serial3(&sercom1, PIN_SERIAL3_RX, PIN_SERIAL3_TX, PAD_SERIAL3_RX, PAD_SERIAL3_TX);
UartT Serial3(false, &sercom1, PIN_SERIAL3_RX, PIN_SERIAL3_TX, PAD_SERIAL3_RX, PAD_SERIAL3_TX);
void SERCOM1_0_Handler() { Serial3.IrqHandler(); }
void SERCOM1_1_Handler() { Serial3.IrqHandler(); }
void SERCOM1_2_Handler() { Serial3.IrqHandler(); }
@ -44,7 +44,7 @@
#endif
#if USING_SERIAL_3
Uart Serial4(&sercom5, PIN_SERIAL4_RX, PIN_SERIAL4_TX, PAD_SERIAL4_RX, PAD_SERIAL4_TX);
UartT Serial4(false, &sercom5, PIN_SERIAL4_RX, PIN_SERIAL4_TX, PAD_SERIAL4_RX, PAD_SERIAL4_TX);
void SERCOM5_0_Handler() { Serial4.IrqHandler(); }
void SERCOM5_1_Handler() { Serial4.IrqHandler(); }
void SERCOM5_2_Handler() { Serial4.IrqHandler(); }

View File

@ -20,6 +20,10 @@
*/
#pragma once
extern Uart Serial2;
extern Uart Serial3;
extern Uart Serial4;
#include "../../core/serial_hook.h"
typedef Serial0Type<Uart> UartT;
extern UartT Serial2;
extern UartT Serial3;
extern UartT Serial4;

View File

@ -28,6 +28,10 @@
#include "../../inc/MarlinConfig.h"
#include "../shared/Delay.h"
#ifdef USBCON
DefaultSerial MSerial(false, SerialUSB);
#endif
#if ENABLED(SRAM_EEPROM_EMULATION)
#if STM32F7xx
#include <stm32f7xx_ll_pwr.h>

View File

@ -39,6 +39,9 @@
#ifdef USBCON
#include <USBSerial.h>
#include "../../core/serial_hook.h"
typedef ForwardSerial0Type< decltype(SerialUSB) > DefaultSerial;
extern DefaultSerial MSerial;
#endif
// ------------------------
@ -48,7 +51,7 @@
#define MSERIAL(X) _MSERIAL(X)
#if SERIAL_PORT == -1
#define MYSERIAL0 SerialUSB
#define MYSERIAL0 MSerial
#elif WITHIN(SERIAL_PORT, 1, 6)
#define MYSERIAL0 MSERIAL(SERIAL_PORT)
#else
@ -57,7 +60,7 @@
#ifdef SERIAL_PORT_2
#if SERIAL_PORT_2 == -1
#define MYSERIAL1 SerialUSB
#define MYSERIAL1 MSerial
#elif WITHIN(SERIAL_PORT_2, 1, 6)
#define MYSERIAL1 MSERIAL(SERIAL_PORT_2)
#else
@ -67,7 +70,7 @@
#ifdef MMU2_SERIAL_PORT
#if MMU2_SERIAL_PORT == -1
#define MMU2_SERIAL SerialUSB
#define MMU2_SERIAL MSerial
#elif WITHIN(MMU2_SERIAL_PORT, 1, 6)
#define MMU2_SERIAL MSERIAL(MMU2_SERIAL_PORT)
#else
@ -77,7 +80,7 @@
#ifdef LCD_SERIAL_PORT
#if LCD_SERIAL_PORT == -1
#define LCD_SERIAL SerialUSB
#define LCD_SERIAL MSerial
#elif WITHIN(LCD_SERIAL_PORT, 1, 6)
#define LCD_SERIAL MSERIAL(LCD_SERIAL_PORT)
#else

View File

@ -35,7 +35,7 @@
#define DECLARE_SERIAL_PORT(ser_num) \
void _rx_complete_irq_ ## ser_num (serial_t * obj); \
MarlinSerial MSerial ## ser_num (USART ## ser_num, &_rx_complete_irq_ ## ser_num); \
MSerialT MSerial ## ser_num (true, USART ## ser_num, &_rx_complete_irq_ ## ser_num); \
void _rx_complete_irq_ ## ser_num (serial_t * obj) { MSerial ## ser_num ._rx_complete_irq(obj); }
#define DECLARE_SERIAL_PORT_EXP(ser_num) DECLARE_SERIAL_PORT(ser_num)

View File

@ -24,21 +24,15 @@
#include "../../feature/e_parser.h"
#endif
#include "../../core/serial_hook.h"
typedef void (*usart_rx_callback_t)(serial_t * obj);
class MarlinSerial : public HardwareSerial {
public:
struct MarlinSerial : public HardwareSerial {
MarlinSerial(void* peripheral, usart_rx_callback_t rx_callback) :
HardwareSerial(peripheral), _rx_callback(rx_callback)
#if ENABLED(EMERGENCY_PARSER)
, emergency_state(EmergencyParser::State::EP_RESET)
#endif
{ }
#if ENABLED(EMERGENCY_PARSER)
static inline bool emergency_parser_enabled() { return true; }
#endif
void begin(unsigned long baud, uint8_t config);
inline void begin(unsigned long baud) { begin(baud, SERIAL_8N1); }
@ -46,19 +40,17 @@ public:
protected:
usart_rx_callback_t _rx_callback;
#if ENABLED(EMERGENCY_PARSER)
EmergencyParser::State emergency_state;
#endif
};
extern MarlinSerial MSerial1;
extern MarlinSerial MSerial2;
extern MarlinSerial MSerial3;
extern MarlinSerial MSerial4;
extern MarlinSerial MSerial5;
extern MarlinSerial MSerial6;
extern MarlinSerial MSerial7;
extern MarlinSerial MSerial8;
extern MarlinSerial MSerial9;
extern MarlinSerial MSerial10;
extern MarlinSerial MSerialLP1;
typedef Serial0Type<MarlinSerial> MSerialT;
extern MSerialT MSerial1;
extern MSerialT MSerial2;
extern MSerialT MSerial3;
extern MSerialT MSerial4;
extern MSerialT MSerial5;
extern MSerialT MSerial6;
extern MSerialT MSerial7;
extern MSerialT MSerial8;
extern MSerialT MSerial9;
extern MSerialT MSerial10;
extern MSerialT MSerialLP1;

View File

@ -84,6 +84,7 @@
#if defined(SERIAL_USB) && !HAS_SD_HOST_DRIVE
USBSerial SerialUSB;
DefaultSerial MSerial(false, SerialUSB);
#endif
uint16_t HAL_adc_result;

View File

@ -61,8 +61,11 @@
#endif
#ifdef SERIAL_USB
typedef ForwardSerial0Type< USBSerial > DefaultSerial;
extern DefaultSerial MSerial;
#if !HAS_SD_HOST_DRIVE
#define UsbSerial Serial
#define UsbSerial MSerial
#else
#define UsbSerial MarlinCompositeSerial
#endif

View File

@ -28,7 +28,7 @@
// Copied from ~/.platformio/packages/framework-arduinoststm32-maple/STM32F1/system/libmaple/usart_private.h
// Changed to handle Emergency Parser
static inline __always_inline void my_usart_irq(ring_buffer *rb, ring_buffer *wb, usart_reg_map *regs, MarlinSerial &serial) {
static inline __always_inline void my_usart_irq(ring_buffer *rb, ring_buffer *wb, usart_reg_map *regs, MSerialT &serial) {
/* Handle RXNEIE and TXEIE interrupts.
* RXNE signifies availability of a byte in DR.
*
@ -90,20 +90,20 @@ constexpr bool serial_handles_emergency(int port) {
;
}
#define DEFINE_HWSERIAL_MARLIN(name, n) \
MarlinSerial name(USART##n, \
BOARD_USART##n##_TX_PIN, \
BOARD_USART##n##_RX_PIN, \
serial_handles_emergency(n)); \
extern "C" void __irq_usart##n(void) { \
#define DEFINE_HWSERIAL_MARLIN(name, n) \
MSerialT name(serial_handles_emergency(n),\
USART##n, \
BOARD_USART##n##_TX_PIN, \
BOARD_USART##n##_RX_PIN); \
extern "C" void __irq_usart##n(void) { \
my_usart_irq(USART##n->rb, USART##n->wb, USART##n##_BASE, MSerial##n); \
}
#define DEFINE_HWSERIAL_UART_MARLIN(name, n) \
MarlinSerial name(UART##n, \
MSerialT name(serial_handles_emergency(n), \
UART##n, \
BOARD_USART##n##_TX_PIN, \
BOARD_USART##n##_RX_PIN, \
serial_handles_emergency(n)); \
BOARD_USART##n##_RX_PIN); \
extern "C" void __irq_usart##n(void) { \
my_usart_irq(UART##n->rb, UART##n->wb, UART##n##_BASE, MSerial##n); \
}

View File

@ -26,28 +26,13 @@
#include <WString.h>
#include "../../inc/MarlinConfigPre.h"
#if ENABLED(EMERGENCY_PARSER)
#include "../../feature/e_parser.h"
#endif
#include "../../core/serial_hook.h"
// Increase priority of serial interrupts, to reduce overflow errors
#define UART_IRQ_PRIO 1
class MarlinSerial : public HardwareSerial {
public:
#if ENABLED(EMERGENCY_PARSER)
const bool ep_enabled;
EmergencyParser::State emergency_state;
inline bool emergency_parser_enabled() { return ep_enabled; }
#endif
MarlinSerial(struct usart_dev *usart_device, uint8 tx_pin, uint8 rx_pin, bool TERN_(EMERGENCY_PARSER, ep_capable)) :
HardwareSerial(usart_device, tx_pin, rx_pin)
#if ENABLED(EMERGENCY_PARSER)
, ep_enabled(ep_capable)
, emergency_state(EmergencyParser::State::EP_RESET)
#endif
{ }
struct MarlinSerial : public HardwareSerial {
MarlinSerial(struct usart_dev *usart_device, uint8 tx_pin, uint8 rx_pin) : HardwareSerial(usart_device, tx_pin, rx_pin) { }
#ifdef UART_IRQ_PRIO
// Shadow the parent methods to set IRQ priority after begin()
@ -62,10 +47,12 @@ public:
#endif
};
extern MarlinSerial MSerial1;
extern MarlinSerial MSerial2;
extern MarlinSerial MSerial3;
typedef Serial0Type<MarlinSerial> MSerialT;
extern MSerialT MSerial1;
extern MSerialT MSerial2;
extern MSerialT MSerial3;
#if EITHER(STM32_HIGH_DENSITY, STM32_XL_DENSITY)
extern MarlinSerial MSerial4;
extern MarlinSerial MSerial5;
extern MSerialT MSerial4;
extern MSerialT MSerial5;
#endif

View File

@ -23,7 +23,7 @@
#define PRODUCT_ID 0x29
USBMassStorage MarlinMSC;
MarlinUSBCompositeSerial MarlinCompositeSerial;
Serial0Type<USBCompositeSerial> MarlinCompositeSerial(true);
#include "../../inc/MarlinConfig.h"

View File

@ -18,25 +18,9 @@
#include <USBComposite.h>
#include "../../inc/MarlinConfigPre.h"
#if ENABLED(EMERGENCY_PARSER)
#include "../../feature/e_parser.h"
#endif
class MarlinUSBCompositeSerial : public USBCompositeSerial {
public:
MarlinUSBCompositeSerial() : USBCompositeSerial()
#if ENABLED(EMERGENCY_PARSER)
, emergency_state(EmergencyParser::State::EP_RESET)
#endif
{ }
#if ENABLED(EMERGENCY_PARSER)
EmergencyParser::State emergency_state;
inline bool emergency_parser_enabled() { return true; }
#endif
};
#include "../../core/serial_hook.h"
extern USBMassStorage MarlinMSC;
extern MarlinUSBCompositeSerial MarlinCompositeSerial;
extern Serial0Type<USBCompositeSerial> MarlinCompositeSerial;
void MSC_SD_init();

View File

@ -31,6 +31,9 @@
#include <Wire.h>
DefaultSerial MSerial(false);
USBSerialType USBSerial(false, SerialUSB);
uint16_t HAL_adc_result;
static const uint8_t pin2sc1a[] = {

View File

@ -50,12 +50,18 @@
#define IS_TEENSY32 1
#endif
#define _MSERIAL(X) Serial##X
#include "../../core/serial_hook.h"
typedef Serial0Type<decltype(Serial)> DefaultSerial;
extern DefaultSerial MSerial;
typedef ForwardSerial0Type<decltype(SerialUSB)> USBSerialType;
extern USBSerialType USBSerial;
#define _MSERIAL(X) MSerial##X
#define MSERIAL(X) _MSERIAL(X)
#define Serial0 Serial
#define MSerial0 MSerial
#if SERIAL_PORT == -1
#define MYSERIAL0 SerialUSB
#define MYSERIAL0 USBSerial
#elif WITHIN(SERIAL_PORT, 0, 3)
#define MYSERIAL0 MSERIAL(SERIAL_PORT)
#endif

View File

@ -31,6 +31,9 @@
#include <Wire.h>
DefaultSerial MSerial(false);
USBSerialType USBSerial(false, SerialUSB);
uint16_t HAL_adc_result, HAL_adc_select;
static const uint8_t pin2sc1a[] = {

View File

@ -53,12 +53,18 @@
#define IS_TEENSY35 1
#endif
#define _MSERIAL(X) Serial##X
#include "../../core/serial_hook.h"
typedef Serial0Type<decltype(Serial)> DefaultSerial;
extern DefaultSerial MSerial;
typedef ForwardSerial0Type<decltype(SerialUSB)> USBSerialType;
extern USBSerialType USBSerial;
#define _MSERIAL(X) MSerial##X
#define MSERIAL(X) _MSERIAL(X)
#define Serial0 Serial
#define MSerial0 MSerial
#if SERIAL_PORT == -1
#define MYSERIAL0 SerialUSB
#define MYSERIAL0 USBSerial
#elif WITHIN(SERIAL_PORT, 0, 3)
#define MYSERIAL0 MSERIAL(SERIAL_PORT)
#endif

View File

@ -32,6 +32,9 @@
#include <Wire.h>
DefaultSerial MSerial(false);
USBSerialType USBSerial(false, SerialUSB);
uint16_t HAL_adc_result, HAL_adc_select;
static const uint8_t pin2sc1a[] = {

View File

@ -37,6 +37,10 @@
#include <stdint.h>
#include <util/atomic.h>
#if HAS_ETHERNET
#include "../../feature/ethernet.h"
#endif
//#define ST7920_DELAY_1 DELAY_NS(600)
//#define ST7920_DELAY_2 DELAY_NS(750)
//#define ST7920_DELAY_3 DELAY_NS(750)
@ -51,9 +55,15 @@
#define IS_TEENSY41 1
#endif
#define _MSERIAL(X) Serial##X
#include "../../core/serial_hook.h"
typedef Serial0Type<decltype(Serial)> DefaultSerial;
extern DefaultSerial MSerial;
typedef ForwardSerial0Type<decltype(SerialUSB)> USBSerialType;
extern USBSerialType USBSerial;
#define _MSERIAL(X) MSerial##X
#define MSERIAL(X) _MSERIAL(X)
#define Serial0 Serial
#define MSerial0 MSerial
#if SERIAL_PORT == -1
#define MYSERIAL0 SerialUSB

View File

@ -920,12 +920,12 @@ void setup() {
MYSERIAL0.begin(BAUDRATE);
millis_t serial_connect_timeout = millis() + 1000UL;
while (!MYSERIAL0 && PENDING(millis(), serial_connect_timeout)) { /*nada*/ }
while (!MYSERIAL0.connected() && PENDING(millis(), serial_connect_timeout)) { /*nada*/ }
#if HAS_MULTI_SERIAL && !HAS_ETHERNET
MYSERIAL1.begin(BAUDRATE);
serial_connect_timeout = millis() + 1000UL;
while (!MYSERIAL1 && PENDING(millis(), serial_connect_timeout)) { /*nada*/ }
while (!MYSERIAL1.connected() && PENDING(millis(), serial_connect_timeout)) { /*nada*/ }
#endif
SERIAL_ECHOLNPGM("start");

View File

@ -314,6 +314,32 @@
#endif
// C++11 solution that is standard compliant. <type_traits> is not available on all platform
namespace Private {
template<bool, typename _Tp = void> struct enable_if { };
template<typename _Tp> struct enable_if<true, _Tp> { typedef _Tp type; };
}
// C++11 solution using SFINAE to detect the existance of a member in a class at compile time.
// It creates a HasMember<Type> structure containing 'value' set to true if the member exists
#define HAS_MEMBER_IMPL(Member) \
namespace Private { \
template <typename Type, typename Yes=char, typename No=long> struct HasMember_ ## Member { \
template <typename C> static Yes& test( decltype(&C::Member) ) ; \
template <typename C> static No& test(...); \
enum { value = sizeof(test<Type>(0)) == sizeof(Yes) }; }; \
}
// Call the method if it exists, but do nothing if it does not. The method is detected at compile time.
// If the method exists, this is inlined and does not cost anything. Else, an "empty" wrapper is created, returning a default value
#define CALL_IF_EXISTS_IMPL(Return, Method, ...) \
HAS_MEMBER_IMPL(Method) \
namespace Private { \
template <typename T, typename ... Args> FORCE_INLINE typename enable_if<HasMember_ ## Method <T>::value, Return>::type Call_ ## Method(T * t, Args... a) { return static_cast<Return>(t->Method(a...)); } \
_UNUSED static Return Call_ ## Method(...) { return __VA_ARGS__; } \
}
#define CALL_IF_EXISTS(Return, That, Method, ...) \
static_cast<Return>(Private::Call_ ## Method(That, ##__VA_ARGS__))
#else
#define MIN_2(a,b) ((a)<(b)?(a):(b))

View File

@ -23,6 +23,10 @@
#include "serial.h"
#include "../inc/MarlinConfig.h"
#if HAS_ETHERNET
#include "../feature/ethernet.h"
#endif
uint8_t marlin_debug_flags = MARLIN_DEBUG_NONE;
// Commonly-used strings in serial output
@ -34,7 +38,18 @@ PGMSTR(SP_X_STR, " X"); PGMSTR(SP_Y_STR, " Y"); PGMSTR(SP_Z_STR, " Z"); PGMST
PGMSTR(SP_X_LBL, " X:"); PGMSTR(SP_Y_LBL, " Y:"); PGMSTR(SP_Z_LBL, " Z:"); PGMSTR(SP_E_LBL, " E:");
#if HAS_MULTI_SERIAL
serial_index_t serial_port_index = 0;
#ifdef SERIAL_CATCHALL
SerialOutputT multiSerial(MYSERIAL, SERIAL_CATCHALL);
#else
#if HAS_ETHERNET
// Runtime checking of the condition variable
ConditionalSerial<decltype(MYSERIAL1)> serialOut1(ethernet.have_telnet_client, MYSERIAL1, false); // Takes reference here
#else
// Don't pay for runtime checking a true variable, instead use the output directly
#define serialOut1 MYSERIAL1
#endif
SerialOutputT multiSerial(MYSERIAL0, serialOut1);
#endif
#endif
void serialprintPGM(PGM_P str) {

View File

@ -22,10 +22,7 @@
#pragma once
#include "../inc/MarlinConfig.h"
#if HAS_ETHERNET
#include "../feature/ethernet.h"
#endif
#include "serial_hook.h"
// Commonly-used strings in serial output
extern const char NUL_STR[], SP_P_STR[], SP_T_STR[],
@ -62,40 +59,33 @@ extern uint8_t marlin_debug_flags;
// Serial redirection
//
typedef int8_t serial_index_t;
#define SERIAL_BOTH 0x7F
#define SERIAL_ALL 0x7F
#if HAS_MULTI_SERIAL
extern serial_index_t serial_port_index;
#define _PORT_REDIRECT(n,p) REMEMBER(n,serial_port_index,p)
#define _PORT_RESTORE(n) RESTORE(n)
#define _PORT_REDIRECT(n,p) REMEMBER(n,multiSerial.portMask,p)
#define SERIAL_ASSERT(P) if(multiSerial.portMask!=(P)){ debugger(); }
#ifdef SERIAL_CATCHALL
#define SERIAL_OUT(WHAT, V...) (void)CAT(MYSERIAL,SERIAL_CATCHALL).WHAT(V)
typedef MultiSerial<decltype(MYSERIAL), decltype(SERIAL_CATCHALL), 0> SerialOutputT;
#else
#define SERIAL_OUT(WHAT, V...) do{ \
const bool port2_open = TERN1(HAS_ETHERNET, ethernet.have_telnet_client); \
if ( serial_port_index == 0 || serial_port_index == SERIAL_BOTH) (void)MYSERIAL0.WHAT(V); \
if ((serial_port_index == 1 || serial_port_index == SERIAL_BOTH) && port2_open) (void)MYSERIAL1.WHAT(V); \
}while(0)
typedef MultiSerial<decltype(MYSERIAL0), TERN(HAS_ETHERNET, ConditionalSerial<decltype(MYSERIAL1)>, decltype(MYSERIAL1)), 0> SerialOutputT;
#endif
#define SERIAL_ASSERT(P) if(serial_port_index!=(P)){ debugger(); }
extern SerialOutputT multiSerial;
#define SERIAL_IMPL multiSerial
#else
#define _PORT_REDIRECT(n,p) NOOP
#define _PORT_RESTORE(n) NOOP
#define SERIAL_OUT(WHAT, V...) (void)MYSERIAL0.WHAT(V)
#define SERIAL_ASSERT(P) NOOP
#define SERIAL_IMPL MYSERIAL0
#endif
#define SERIAL_OUT(WHAT, V...) (void)SERIAL_IMPL.WHAT(V)
#define PORT_REDIRECT(p) _PORT_REDIRECT(1,p)
#define PORT_RESTORE() _PORT_RESTORE(1)
#define SERIAL_PORTMASK(P) _BV(P)
#define SERIAL_ECHO(x) SERIAL_OUT(print, x)
#define SERIAL_ECHO_F(V...) SERIAL_OUT(print, V)
#define SERIAL_ECHOLN(x) SERIAL_OUT(println, x)
#define SERIAL_PRINT(x,b) SERIAL_OUT(print, x, b)
#define SERIAL_PRINTLN(x,b) SERIAL_OUT(println, x, b)
#define SERIAL_PRINTF(V...) SERIAL_OUT(printf, V)
#define SERIAL_FLUSH() SERIAL_OUT(flush)
#ifdef ARDUINO_ARCH_STM32

View File

@ -0,0 +1,146 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "../inc/MarlinConfigPre.h"
#if ENABLED(EMERGENCY_PARSER)
#include "../feature/e_parser.h"
#endif
#ifndef DEC
#define DEC 10
#define HEX 16
#define OCT 8
#define BIN 2
#endif
// flushTX is not implemented in all HAL, so use SFINAE to call the method where it is.
CALL_IF_EXISTS_IMPL(void, flushTX );
CALL_IF_EXISTS_IMPL(bool, connected, true);
// Using Curiously Recurring Template Pattern here to avoid virtual table cost when compiling.
// Since the real serial class is known at compile time, this results in compiler writing a completely
// efficient code
template <class Child>
struct SerialBase {
#if ENABLED(EMERGENCY_PARSER)
const bool ep_enabled;
EmergencyParser::State emergency_state;
inline bool emergency_parser_enabled() { return ep_enabled; }
SerialBase(bool ep_capable) : ep_enabled(ep_capable), emergency_state(EmergencyParser::State::EP_RESET) {}
#else
SerialBase(const bool) {}
#endif
// Static dispatch methods below:
// The most important method here is where it all ends to:
size_t write(uint8_t c) { return static_cast<Child*>(this)->write(c); }
// Called when the parser finished processing an instruction, usually build to nothing
void msgDone() { static_cast<Child*>(this)->msgDone(); }
// Called upon initialization
void begin(const long baudRate) { static_cast<Child*>(this)->begin(baudRate); }
// Called upon destruction
void end() { static_cast<Child*>(this)->end(); }
/** Check for available data from the port
@param index The port index, usually 0 */
bool available(uint8_t index = 0) { return static_cast<Child*>(this)->available(index); }
/** Read a value from the port
@param index The port index, usually 0 */
int read(uint8_t index = 0) { return static_cast<Child*>(this)->read(index); }
// Check if the serial port is connected (usually bypassed)
bool connected() { return static_cast<Child*>(this)->connected(); }
// Redirect flush
void flush() { static_cast<Child*>(this)->flush(); }
// Not all implementation have a flushTX, so let's call them only if the child has the implementation
void flushTX() { CALL_IF_EXISTS(void, static_cast<Child*>(this), flushTX); }
// Glue code here
FORCE_INLINE void write(const char* str) { while (*str) write(*str++); }
FORCE_INLINE void write(const uint8_t* buffer, size_t size) { while (size--) write(*buffer++); }
FORCE_INLINE void print(const char* str) { write(str); }
FORCE_INLINE void print(char c, int base = 0) { print((long)c, base); }
FORCE_INLINE void print(unsigned char c, int base = 0) { print((unsigned long)c, base); }
FORCE_INLINE void print(int c, int base = DEC) { print((long)c, base); }
FORCE_INLINE void print(unsigned int c, int base = DEC) { print((unsigned long)c, base); }
void print(long c, int base = DEC) { if (!base) write(c); write((const uint8_t*)"-", c < 0); printNumber(c < 0 ? -c : c, base); }
void print(unsigned long c, int base = DEC) { printNumber(c, base); }
void print(double c, int digits = 2) { printFloat(c, digits); }
FORCE_INLINE void println(const char s[]) { print(s); println(); }
FORCE_INLINE void println(char c, int base = 0) { print(c, base); println(); }
FORCE_INLINE void println(unsigned char c, int base = 0) { print(c, base); println(); }
FORCE_INLINE void println(int c, int base = DEC) { print(c, base); println(); }
FORCE_INLINE void println(unsigned int c, int base = DEC) { print(c, base); println(); }
FORCE_INLINE void println(long c, int base = DEC) { print(c, base); println(); }
FORCE_INLINE void println(unsigned long c, int base = DEC) { print(c, base); println(); }
FORCE_INLINE void println(double c, int digits = 2) { print(c, digits); println(); }
void println() { write("\r\n"); }
// Print a number with the given base
void printNumber(unsigned long n, const uint8_t base) {
if (n) {
unsigned char buf[8 * sizeof(long)]; // Enough space for base 2
int8_t i = 0;
while (n) {
buf[i++] = n % base;
n /= base;
}
while (i--) write((char)(buf[i] + (buf[i] < 10 ? '0' : 'A' - 10)));
}
else write('0');
}
// Print a decimal number
void printFloat(double number, uint8_t digits) {
// Handle negative numbers
if (number < 0.0) {
write('-');
number = -number;
}
// Round correctly so that print(1.999, 2) prints as "2.00"
double rounding = 0.5;
LOOP_L_N(i, digits) rounding *= 0.1;
number += rounding;
// Extract the integer part of the number and print it
unsigned long int_part = (unsigned long)number;
double remainder = number - (double)int_part;
printNumber(int_part, 10);
// Print the decimal point, but only if there are digits beyond
if (digits) {
write('.');
// Extract digits from the remainder one at a time
while (digits--) {
remainder *= 10.0;
int toPrint = int(remainder);
printNumber(toPrint, 10);
remainder -= toPrint;
}
}
}
};
// All serial instances will be built by chaining the features required for the function in a form of a template
// type definition

View File

@ -0,0 +1,230 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "serial_base.h"
// The most basic serial class: it dispatch to the base serial class with no hook whatsoever. This will compile to nothing but the base serial class
template <class SerialT>
struct BaseSerial : public SerialBase< BaseSerial<SerialT> >, public SerialT {
typedef SerialBase< BaseSerial<SerialT> > BaseClassT;
// It's required to implement a write method here to help compiler disambiguate what method to call
using SerialT::write;
using SerialT::flush;
void msgDone() {}
bool available(uint8_t index) { return index == 0 && SerialT::available(); }
int read(uint8_t index) { return index == 0 ? SerialT::read() : -1; }
bool connected() { return CALL_IF_EXISTS(bool, static_cast<SerialT*>(this), connected);; }
// We have 2 implementation of the same method in both base class, let's say which one we want
using SerialT::available;
using SerialT::read;
using SerialT::begin;
using SerialT::end;
using BaseClassT::print;
using BaseClassT::println;
BaseSerial(const bool e) : BaseClassT(e) {}
// Forward constructor
template <typename... Args>
BaseSerial(const bool e, Args... args) : BaseClassT(e), SerialT(args...) {}
};
// A serial with a condition checked at runtime for its output
// A bit less efficient than static dispatching but since it's only used for ethernet's serial output right now, it's ok.
template <class SerialT>
struct ConditionalSerial : public SerialBase< ConditionalSerial<SerialT> > {
typedef SerialBase< ConditionalSerial<SerialT> > BaseClassT;
bool & condition;
SerialT & out;
size_t write(uint8_t c) { if (condition) return out.write(c); return 0; }
void flush() { if (condition) out.flush(); }
void begin(long br) { out.begin(br); }
void end() { out.end(); }
void msgDone() {}
bool connected() { return CALL_IF_EXISTS(bool, &out, connected); }
bool available(uint8_t index) { return index == 0 && out.available(); }
int read(uint8_t index) { return index == 0 ? out.read() : -1; }
using BaseClassT::available;
using BaseClassT::read;
ConditionalSerial(bool & conditionVariable, SerialT & out, const bool e) : BaseClassT(e), condition(conditionVariable), out(out) {}
};
// A simple foward class that taking a reference to an existing serial instance (likely created in their respective framework)
template <class SerialT>
struct ForwardSerial : public SerialBase< ForwardSerial<SerialT> > {
typedef SerialBase< ForwardSerial<SerialT> > BaseClassT;
SerialT & out;
size_t write(uint8_t c) { return out.write(c); }
void flush() { out.flush(); }
void begin(long br) { out.begin(br); }
void end() { out.end(); }
void msgDone() {}
// Existing instances implement Arduino's operator bool, so use that if it's available
bool connected() { return Private::HasMember_connected<SerialT>::value ? CALL_IF_EXISTS(bool, &out, connected) : (bool)out; }
bool available(uint8_t index) { return index == 0 && out.available(); }
int read(uint8_t index) { return index == 0 ? out.read() : -1; }
bool available() { return out.available(); }
int read() { return out.read(); }
ForwardSerial(const bool e, SerialT & out) : BaseClassT(e), out(out) {}
};
// A class that's can be hooked and unhooked at runtime, useful to capturing the output of the serial interface
template <class SerialT>
struct RuntimeSerial : public SerialBase< RuntimeSerial<SerialT> >, public SerialT {
typedef SerialBase< RuntimeSerial<SerialT> > BaseClassT;
typedef void (*WriteHook)(void * userPointer, uint8_t c);
typedef void (*EndOfMessageHook)(void * userPointer);
WriteHook writeHook;
EndOfMessageHook eofHook;
void * userPointer;
size_t write(uint8_t c) {
if (writeHook) writeHook(userPointer, c);
return SerialT::write(c);
}
void msgDone() {
if (eofHook) eofHook(userPointer);
}
bool available(uint8_t index) { return index == 0 && SerialT::available(); }
int read(uint8_t index) { return index == 0 ? SerialT::read() : -1; }
using SerialT::available;
using SerialT::read;
using SerialT::flush;
using SerialT::begin;
using SerialT::end;
using BaseClassT::print;
using BaseClassT::println;
void setHook(WriteHook writeHook = 0, EndOfMessageHook eofHook = 0, void * userPointer = 0) {
// Order is important here as serial code can be called inside interrupts
// When setting a hook, the user pointer must be set first so if writeHook is called as soon as it's set, it'll be valid
if (userPointer) this->userPointer = userPointer;
this->writeHook = writeHook;
this->eofHook = eofHook;
// Order is important here because of asynchronous access here
// When unsetting a hook, the user pointer must be unset last so that any pending writeHook is still using the old pointer
if (!userPointer) this->userPointer = 0;
}
RuntimeSerial(const bool e) : BaseClassT(e), writeHook(0), eofHook(0), userPointer(0) {}
// Forward constructor
template <typename... Args>
RuntimeSerial(const bool e, Args... args) : BaseClassT(e), SerialT(args...) {}
};
// A class that's duplicating its output conditionally to 2 serial interface
template <class Serial0T, class Serial1T, const uint8_t offset = 0>
struct MultiSerial : public SerialBase< MultiSerial<Serial0T, Serial1T, offset> > {
typedef SerialBase< MultiSerial<Serial0T, Serial1T, offset> > BaseClassT;
uint8_t portMask;
Serial0T & serial0;
Serial1T & serial1;
enum Masks {
FirstOutputMask = (1 << offset),
SecondOutputMask = (1 << (offset + 1)),
AllMask = FirstOutputMask | SecondOutputMask,
};
size_t write(uint8_t c) {
size_t ret = 0;
if (portMask & FirstOutputMask) ret = serial0.write(c);
if (portMask & SecondOutputMask) ret = serial1.write(c) | ret;
return ret;
}
void msgDone() {
if (portMask & FirstOutputMask) serial0.msgDone();
if (portMask & SecondOutputMask) serial1.msgDone();
}
bool available(uint8_t index) {
switch(index) {
case 0 + offset: return serial0.available();
case 1 + offset: return serial1.available();
default: return false;
}
}
int read(uint8_t index) {
switch(index) {
case 0 + offset: return serial0.read();
case 1 + offset: return serial1.read();
default: return -1;
}
}
void begin(const long br) {
if (portMask & FirstOutputMask) serial0.begin(br);
if (portMask & SecondOutputMask) serial1.begin(br);
}
void end() {
if (portMask & FirstOutputMask) serial0.end();
if (portMask & SecondOutputMask) serial1.end();
}
bool connected() {
bool ret = true;
if (portMask & FirstOutputMask) ret = CALL_IF_EXISTS(bool, &serial0, connected);
if (portMask & SecondOutputMask) ret = ret && CALL_IF_EXISTS(bool, &serial1, connected);
return ret;
}
using BaseClassT::available;
using BaseClassT::read;
// Redirect flush
void flush() {
if (portMask & FirstOutputMask) serial0.flush();
if (portMask & SecondOutputMask) serial1.flush();
}
void flushTX() {
if (portMask & FirstOutputMask) CALL_IF_EXISTS(void, &serial0, flushTX);
if (portMask & SecondOutputMask) CALL_IF_EXISTS(void, &serial1, flushTX);
}
MultiSerial(Serial0T & serial0, Serial1T & serial1, int8_t mask = AllMask, const bool e = false) :
BaseClassT(e),
portMask(mask), serial0(serial0), serial1(serial1) {}
};
// Build the actual serial object depending on current configuration
#define Serial0Type TERN(SERIAL_RUNTIME_HOOK, RuntimeSerial, BaseSerial)
#define ForwardSerial0Type TERN(SERIAL_RUNTIME_HOOK, RuntimeSerial, ForwardSerial)
#ifdef HAS_MULTI_SERIAL
#define Serial1Type ConditionalSerial
#endif

View File

@ -30,23 +30,11 @@
#endif
inline bool bs_serial_data_available(const uint8_t index) {
switch (index) {
case 0: return MYSERIAL0.available();
#if HAS_MULTI_SERIAL
case 1: return MYSERIAL1.available();
#endif
}
return false;
return SERIAL_IMPL.available(index);
}
inline int bs_read_serial(const uint8_t index) {
switch (index) {
case 0: return MYSERIAL0.read();
#if HAS_MULTI_SERIAL
case 1: return MYSERIAL1.read();
#endif
}
return -1;
return SERIAL_IMPL.read(index);
}
#if ENABLED(BINARY_STREAM_COMPRESSION)
@ -297,7 +285,7 @@ public:
millis_t transfer_window = millis() + RX_TIMESLICE;
#if ENABLED(SDSUPPORT)
PORT_REDIRECT(card.transfer_port_index);
PORT_REDIRECT(SERIAL_PORTMASK(card.transfer_port_index));
#endif
#pragma GCC diagnostic push

View File

@ -38,7 +38,7 @@
#endif
void host_action(PGM_P const pstr, const bool eol) {
PORT_REDIRECT(SERIAL_BOTH);
PORT_REDIRECT(SERIAL_ALL);
SERIAL_ECHOPGM("//action:");
serialprintPGM(pstr);
if (eol) SERIAL_EOL();
@ -78,20 +78,20 @@ void host_action(PGM_P const pstr, const bool eol) {
PromptReason host_prompt_reason = PROMPT_NOT_DEFINED;
void host_action_notify(const char * const message) {
PORT_REDIRECT(SERIAL_BOTH);
PORT_REDIRECT(SERIAL_ALL);
host_action(PSTR("notification "), false);
SERIAL_ECHOLN(message);
}
void host_action_notify_P(PGM_P const message) {
PORT_REDIRECT(SERIAL_BOTH);
PORT_REDIRECT(SERIAL_ALL);
host_action(PSTR("notification "), false);
serialprintPGM(message);
SERIAL_EOL();
}
void host_action_prompt(PGM_P const ptype, const bool eol=true) {
PORT_REDIRECT(SERIAL_BOTH);
PORT_REDIRECT(SERIAL_ALL);
host_action(PSTR("prompt_"), false);
serialprintPGM(ptype);
if (eol) SERIAL_EOL();
@ -99,7 +99,7 @@ void host_action(PGM_P const pstr, const bool eol) {
void host_action_prompt_plus(PGM_P const ptype, PGM_P const pstr, const char extra_char='\0') {
host_action_prompt(ptype, false);
PORT_REDIRECT(SERIAL_BOTH);
PORT_REDIRECT(SERIAL_ALL);
SERIAL_CHAR(' ');
serialprintPGM(pstr);
if (extra_char != '\0') SERIAL_CHAR(extra_char);

View File

@ -47,7 +47,8 @@ void MAC_report() {
Ethernet.MACAddress(mac);
SERIAL_ECHOPGM(" MAC: ");
LOOP_L_N(i, 6) {
SERIAL_PRINTF("%02X", mac[i]);
if (mac[i] < 16) SERIAL_CHAR('0');
SERIAL_PRINT(mac[i], HEX);
if (i < 5) SERIAL_CHAR(':');
}
}

View File

@ -986,6 +986,8 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
}
if (!no_ok) queue.ok_to_send();
SERIAL_OUT(msgDone); // Call the msgDone serial hook to signal command processing done
}
/**
@ -995,7 +997,7 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
void GcodeSuite::process_next_command() {
char * const current_command = queue.command_buffer[queue.index_r];
PORT_REDIRECT(queue.port[queue.index_r]);
PORT_REDIRECT(SERIAL_PORTMASK(queue.port[queue.index_r]));
#if ENABLED(POWER_LOSS_RECOVERY)
recovery.queue_index_r = queue.index_r;

View File

@ -53,21 +53,14 @@ void GcodeSuite::M118() {
}
#if HAS_MULTI_SERIAL
const serial_index_t old_serial = serial_port_index;
const int8_t old_serial = multiSerial.portMask;
if (WITHIN(port, 0, NUM_SERIAL))
serial_port_index = (
port == 0 ? SERIAL_BOTH
: port == 1 ? 0
#if HAS_MULTI_SERIAL
: port == 2 ? 1
#endif
: SERIAL_PORT
);
multiSerial.portMask = port ? _BV(port - 1) : SERIAL_ALL;
#endif
if (hasE) SERIAL_ECHO_START();
if (hasA) SERIAL_ECHOPGM("//");
SERIAL_ECHOLN(p);
TERN_(HAS_MULTI_SERIAL, serial_port_index = old_serial);
TERN_(HAS_MULTI_SERIAL, multiSerial.portMask = old_serial);
}

View File

@ -290,8 +290,8 @@ void GCodeQueue::enqueue_now_P(PGM_P const pgcode) {
void GCodeQueue::ok_to_send() {
#if HAS_MULTI_SERIAL
const serial_index_t serial_ind = command_port();
if (serial_ind < 0) return; // Never mind. Command came from SD or Flash Drive
PORT_REDIRECT(serial_ind); // Reply to the serial port that sent the command
if (serial_ind < 0) return;
PORT_REDIRECT(SERIAL_PORTMASK(serial_ind)); // Reply to the serial port that sent the command
#endif
if (!send_ok[index_r]) return;
SERIAL_ECHOPGM(STR_OK);
@ -317,7 +317,7 @@ void GCodeQueue::flush_and_request_resend() {
const serial_index_t serial_ind = command_port();
#if HAS_MULTI_SERIAL
if (serial_ind < 0) return; // Never mind. Command came from SD or Flash Drive
PORT_REDIRECT(serial_ind); // Reply to the serial port that sent the command
PORT_REDIRECT(SERIAL_PORTMASK(serial_ind)); // Reply to the serial port that sent the command
#endif
SERIAL_FLUSH();
SERIAL_ECHOPGM(STR_RESEND);
@ -349,11 +349,11 @@ inline int read_serial(const uint8_t index) {
}
void GCodeQueue::gcode_line_error(PGM_P const err, const serial_index_t serial_ind) {
PORT_REDIRECT(serial_ind); // Reply to the serial port that sent the command
PORT_REDIRECT(SERIAL_PORTMASK(serial_ind)); // Reply to the serial port that sent the command
SERIAL_ERROR_START();
serialprintPGM(err);
SERIAL_ECHOLN(last_N[serial_ind]);
while (read_serial(serial_ind) != -1); // Clear out the RX buffer
while (read_serial(serial_ind) != -1); // Clear out the RX buffer
flush_and_request_resend();
serial_count[serial_ind] = 0;
}
@ -547,7 +547,7 @@ void GCodeQueue::get_serial_commands() {
#if ENABLED(BEZIER_CURVE_SUPPORT)
case 5:
#endif
PORT_REDIRECT(p); // Reply to the serial port that sent the command
PORT_REDIRECT(SERIAL_PORTMASK(p)); // Reply to the serial port that sent the command
SERIAL_ECHOLNPGM(STR_ERR_STOPPED);
LCD_MESSAGEPGM(MSG_STOPPED);
break;

View File

@ -82,7 +82,7 @@ void GcodeSuite::M1001() {
// Announce SD file completion
{
PORT_REDIRECT(SERIAL_BOTH);
PORT_REDIRECT(SERIAL_ALL);
SERIAL_ECHOLNPGM(STR_FILE_PRINTED);
}

View File

@ -320,6 +320,7 @@
// FSMC/SPI TFT Panels (LVGL)
#if ENABLED(TFT_LVGL_UI)
#define HAS_TFT_LVGL_UI 1
#define SERIAL_RUNTIME_HOOK 1
#endif
// FSMC/SPI TFT Panels

View File

@ -414,8 +414,8 @@ void update_usb_status(const bool forceUpdate) {
// This is mildly different than stock, which
// appears to use the usb discovery status.
// This is more logical.
if (last_usb_connected_status != MYSERIAL0 || forceUpdate) {
last_usb_connected_status = MYSERIAL0;
if (last_usb_connected_status != MYSERIAL0.connected() || forceUpdate) {
last_usb_connected_status = MYSERIAL0.connected();
write_to_lcd_P(last_usb_connected_status ? PSTR("{R:UC}\r\n") : PSTR("{R:UD}\r\n"));
}
}

View File

@ -253,7 +253,7 @@ const char str_t_thermal_runaway[] PROGMEM = STR_T_THERMAL_RUNAWAY,
*/
void Temperature::report_fan_speed(const uint8_t target) {
if (target >= FAN_COUNT) return;
PORT_REDIRECT(SERIAL_BOTH);
PORT_REDIRECT(SERIAL_ALL);
SERIAL_ECHOLNPAIR("M106 P", target, " S", fan_speed[target]);
}
#endif
@ -3130,7 +3130,7 @@ void Temperature::tick() {
void Temperature::auto_report_temperatures() {
if (auto_report_temp_interval && ELAPSED(millis(), next_temp_report_ms)) {
next_temp_report_ms = millis() + 1000UL * auto_report_temp_interval;
PORT_REDIRECT(SERIAL_BOTH);
PORT_REDIRECT(SERIAL_ALL);
print_heater_states(active_extruder);
SERIAL_EOL();
}

View File

@ -550,12 +550,11 @@ void openFailed(const char * const fname) {
void announceOpen(const uint8_t doing, const char * const path) {
if (doing) {
PORT_REDIRECT(SERIAL_BOTH);
PORT_REDIRECT(SERIAL_ALL);
SERIAL_ECHO_START();
SERIAL_ECHOPGM("Now ");
serialprintPGM(doing == 1 ? PSTR("doing") : PSTR("fresh"));
SERIAL_ECHOLNPAIR(" file: ", path);
PORT_RESTORE();
}
}
@ -617,10 +616,11 @@ void CardReader::openFileRead(char * const path, const uint8_t subcall_type/*=0*
filesize = file.fileSize();
sdpos = 0;
PORT_REDIRECT(SERIAL_BOTH);
SERIAL_ECHOLNPAIR(STR_SD_FILE_OPENED, fname, STR_SD_SIZE, filesize);
SERIAL_ECHOLNPGM(STR_SD_FILE_SELECTED);
PORT_RESTORE();
{ // Don't remove this block, as the PORT_REDIRECT is a RAII
PORT_REDIRECT(SERIAL_ALL);
SERIAL_ECHOLNPAIR(STR_SD_FILE_OPENED, fname, STR_SD_SIZE, filesize);
SERIAL_ECHOLNPGM(STR_SD_FILE_SELECTED);
}
selectFileByName(fname);
ui.set_status(longFilename[0] ? longFilename : fname);

View File

@ -174,7 +174,7 @@ public:
#if ENABLED(AUTO_REPORT_SD_STATUS)
static void auto_report_sd_status();
static inline void set_auto_report_interval(uint8_t v) {
TERN_(HAS_MULTI_SERIAL, auto_report_port = serial_port_index);
TERN_(HAS_MULTI_SERIAL, auto_report_port = serialHook.portMask);
NOMORE(v, 60);
auto_report_sd_interval = v;
next_sd_report_ms = millis() + 1000UL * v;

View File

@ -20,7 +20,7 @@ exec_test () {
if [[ -z "$VERBOSE_PLATFORMIO" ]] ; then
silent="--silent"
else
silent=""
silent="-v"
fi
if platformio run --project-dir $1 -e $2 $silent; then
printf "\033[0;32mPassed\033[0m\n"

44
docs/Serial.md Normal file
View File

@ -0,0 +1,44 @@
# Serial port architecture in Marlin
Marlin is targeting a pletora of different CPU architecture and platforms. Each of these platforms has its own serial interface.
While many provide a Arduino-like Serial class, it's not all of them, and the differences in the existing API create a very complex brain teaser for writing code that works more or less on each platform.
Moreover, many platform have intrinsic needs about serial port (like forwarding the output on multiple serial port, providing a *serial-like* telnet server, mixing USB-based serial port with SD card emulation) that are difficult to handle cleanly in the other platform serial logic.
Starting with version `2.0.9`, Marlin provides a common interface for its serial needs.
## Common interface
This interface is declared in `Marlin/src/core/serial_base.h`
Any implementation will need to follow this interface for being used transparently in Marlin's codebase.
The implementation was written to prioritize performance over abstraction, so the base interface is not using virtual inheritance to avoid the cost of virtual dispatching while calling methods.
Instead, the Curiously Recurring Template Pattern (**CRTP**) is used so that, upon compilation, the interface abstraction does not incur a performance cost.
Because some platform do not follow the same interface, the missing method in the actual low-level implementation are detected via SFINAE and a wrapper is generated when such method are missing. See `CALL_IF_EXISTS` macro in `Marlin/src/core/macros.h` for the documentation of this technic.
## Composing the desired feature
The different specificities for each architecture are provided by composing the serial type based on desired functionality.
In the `Marlin/src/core/serial_hook.h` file, the different serial feature are declared and defined in each templated type:
1. `BaseSerial` is a simple 1:1 wrapper to the underlying, Arduino compatible, `Serial`'s class. It derives from it. You'll use this if the platform does not do anything specific for the `Serial` object (for example, if an interrupt callback calls directly the serial **instance** in the platform's framework code, this is not the right class to use). This wrapper is completely inlined so that it does not generate any code upon compilation. `BaseSerial` constructor forwards any parameter to the platform's `Serial`'s constructor.
2. `ForwardSerial` is a composing wrapper. It references an actual Arduino compatible `Serial` instance. You'll use this if the instance is declared in the platform's framework and is being referred directly in the framework. This is not as efficient as the `BaseSerial` implementation since static dereferencing is done for each method call (it'll still be faster than virtual dispatching)
3. `ConditionalSerial` is working a bit like the `ForwardSerial` interface, but it checks a boolean condition before calling the referenced instance. You'll use it when the serial output can be switch off at runtime, for example in a *telnet* like serial output that should not emit any packet if no client is connected.
4. `RuntimeSerial` is providing a runtime-modifiable hooking method for its `write` and `msgDone` method. You'll use it if you need to capture the serial output of Marlin, for example to display the G-Code parser's output on a GUI interface. The hooking interface is setup via the `setHook` method.
5. `MultiSerial` is a runtime modifiable serial output multiplexer. It can output (*respectively input*) to 2 different interface based on a port *mask*. You'll use this if you need to output the same serial stream to multiple port. You can plug a `MultiSerial` to itself to duplicate to more than 2 ports.
## Plumbing
Since all the types above are using CRTP, it's possible to combine them to get the appropriate functionality.
This is easily done via type definition of the feature.
For example, to present a serial interface that's outputting to 2 serial port, the first one being hooked at runtime and the second one connected to a runtime switchable telnet client, you'll declare the type to use as:
```
typedef MultiSerial< RuntimeSerial<Serial>, ConditionalSerial<TelnetClient> > Serial0Type;
```
## Emergency parser
By default, the serial base interface provide an emergency parser that's only enable for serial classes that support it.
Because of this condition, all underlying type takes a first `bool emergencyParserEnabled` argument to their constructor. You must take into account this parameter when defining the actual type used.
*This document was written by [X-Ryl669](https://blog.cyril.by) and is under [CC-SA license](https://creativecommons.org/licenses/by-sa)*