Browse Source

Combined LPC / Serial fixes (#21178)

Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
X-Ryl669 3 years ago
parent
commit
f003e52009
No account linked to committer's email address

+ 5
- 6
Marlin/src/HAL/DUE/MarlinSerialUSB.cpp View File

92
   return c;
92
   return c;
93
 }
93
 }
94
 
94
 
95
-bool MarlinSerialUSB::available() {
96
-    /* If Pending chars */
97
-  return pending_char >= 0 ||
98
-    /* or USB CDC enumerated and configured on the PC side and some
99
-       bytes where sent to us */
100
-      (usb_task_cdc_isenabled() && udi_cdc_is_rx_ready());
95
+int MarlinSerialUSB::available() {
96
+  if (pending_char > 0) return pending_char;
97
+  return pending_char == 0 ||
98
+    // or USB CDC enumerated and configured on the PC side and some bytes where sent to us */
99
+    (usb_task_cdc_isenabled() && udi_cdc_is_rx_ready());
101
 }
100
 }
102
 
101
 
103
 void MarlinSerialUSB::flush() { }
102
 void MarlinSerialUSB::flush() { }

+ 1
- 1
Marlin/src/HAL/DUE/MarlinSerialUSB.h View File

39
   int peek();
39
   int peek();
40
   int read();
40
   int read();
41
   void flush();
41
   void flush();
42
-  bool available();
42
+  int available();
43
   size_t write(const uint8_t c);
43
   size_t write(const uint8_t c);
44
 
44
 
45
   #if ENABLED(SERIAL_STATS_DROPPED_RX)
45
   #if ENABLED(SERIAL_STATS_DROPPED_RX)

+ 34
- 8
Marlin/src/HAL/LPC1768/MarlinSerial.cpp View File

25
 #include "MarlinSerial.h"
25
 #include "MarlinSerial.h"
26
 
26
 
27
 #if ANY_SERIAL_IS(0)
27
 #if ANY_SERIAL_IS(0)
28
-  MSerialT MSerial(true, LPC_UART0);
29
-  extern "C" void UART0_IRQHandler() { MSerial.IRQHandler(); }
28
+  MarlinSerial _MSerial(LPC_UART0);
29
+  MSerialT MSerial(true, _MSerial);
30
+  extern "C" void UART0_IRQHandler() { _MSerial.IRQHandler(); }
30
 #endif
31
 #endif
31
 #if ANY_SERIAL_IS(1)
32
 #if ANY_SERIAL_IS(1)
32
-  MSerialT MSerial1(true, (LPC_UART_TypeDef *) LPC_UART1);
33
-  extern "C" void UART1_IRQHandler() { MSerial1.IRQHandler(); }
33
+  MarlinSerial _MSerial1((LPC_UART_TypeDef *) LPC_UART1);
34
+  MSerialT MSerial1(true, _MSerial1);
35
+  extern "C" void UART1_IRQHandler() { _MSerial1.IRQHandler(); }
34
 #endif
36
 #endif
35
 #if ANY_SERIAL_IS(2)
37
 #if ANY_SERIAL_IS(2)
36
-  MSerialT MSerial2(true, LPC_UART2);
37
-  extern "C" void UART2_IRQHandler() { MSerial2.IRQHandler(); }
38
+  MarlinSerial _MSerial2(LPC_UART2);
39
+  MSerialT MSerial2(true, _MSerial2);
40
+  extern "C" void UART2_IRQHandler() { _MSerial2.IRQHandler(); }
38
 #endif
41
 #endif
39
 #if ANY_SERIAL_IS(3)
42
 #if ANY_SERIAL_IS(3)
40
-  MSerialT MSerial3(true, LPC_UART3);
41
-  extern "C" void UART3_IRQHandler() { MSerial3.IRQHandler(); }
43
+  MarlinSerial _MSerial3(LPC_UART3);
44
+  MSerialT MSerial3(true, _MSerial3);
45
+  extern "C" void UART3_IRQHandler() { _MSerial3.IRQHandler(); }
46
+#endif
47
+
48
+#if ENABLED(EMERGENCY_PARSER)
49
+
50
+  bool MarlinSerial::recv_callback(const char c) {
51
+    // Need to figure out which serial port we are and react in consequence (Marlin does not have CONTAINER_OF macro)
52
+    if (false) {}
53
+    #if ANY_SERIAL_IS(0)
54
+      else if (this == &_MSerial) emergency_parser.update(MSerial.emergency_state, c);
55
+    #endif
56
+    #if ANY_SERIAL_IS(1)
57
+      else if (this == &_MSerial1) emergency_parser.update(MSerial1.emergency_state, c);
58
+    #endif
59
+    #if ANY_SERIAL_IS(2)
60
+      else if (this == &_MSerial2) emergency_parser.update(MSerial2.emergency_state, c);
61
+    #endif
62
+    #if ANY_SERIAL_IS(3)
63
+      else if (this == &_MSerial3) emergency_parser.update(MSerial3.emergency_state, c);
64
+    #endif
65
+    return true;
66
+  }
67
+
42
 #endif
68
 #endif
43
 
69
 
44
 #endif // TARGET_LPC1768
70
 #endif // TARGET_LPC1768

+ 11
- 5
Marlin/src/HAL/LPC1768/MarlinSerial.h View File

47
   void end() {}
47
   void end() {}
48
 
48
 
49
   #if ENABLED(EMERGENCY_PARSER)
49
   #if ENABLED(EMERGENCY_PARSER)
50
-    bool recv_callback(const char c) override {
51
-      emergency_parser.update(static_cast<Serial0Type<MarlinSerial> *>(this)->emergency_state, c);
52
-      return true; // do not discard character
53
-    }
50
+    bool recv_callback(const char c) override;
54
   #endif
51
   #endif
55
 };
52
 };
56
 
53
 
57
-typedef Serial0Type<MarlinSerial> MSerialT;
54
+// On LPC176x framework, HardwareSerial does not implement the same interface as Arduino's Serial, so overloads
55
+// of 'available' and 'read' method are not used in this multiple inheritance scenario.
56
+// Instead, use a ForwardSerial here that adapts the interface.
57
+typedef ForwardSerial0Type<MarlinSerial> MSerialT;
58
 extern MSerialT MSerial;
58
 extern MSerialT MSerial;
59
 extern MSerialT MSerial1;
59
 extern MSerialT MSerial1;
60
 extern MSerialT MSerial2;
60
 extern MSerialT MSerial2;
61
 extern MSerialT MSerial3;
61
 extern MSerialT MSerial3;
62
+
63
+// Consequently, we can't use a RuntimeSerial either. The workaround would be to use a RuntimeSerial<ForwardSerial<MarlinSerial>> type here
64
+// Right now, let's ignore this until it's actually required.
65
+#if ENABLED(SERIAL_RUNTIME_HOOK)
66
+  #error "SERIAL_RUNTIME_HOOK is not yet supported for LPC176x."
67
+#endif

+ 2
- 2
Marlin/src/HAL/LPC1768/usb_serial.cpp View File

29
 
29
 
30
 EmergencyParser::State emergency_state;
30
 EmergencyParser::State emergency_state;
31
 
31
 
32
-bool CDC_RecvCallback(const char buffer) {
33
-  emergency_parser.update(emergency_state, buffer);
32
+bool CDC_RecvCallback(const char c) {
33
+  emergency_parser.update(emergency_state, c);
34
   return true;
34
   return true;
35
 }
35
 }
36
 
36
 

+ 2
- 2
Marlin/src/HAL/shared/Delay.cpp View File

51
   // Use hardware cycle counter instead, it's much safer
51
   // Use hardware cycle counter instead, it's much safer
52
   void delay_dwt(uint32_t count) {
52
   void delay_dwt(uint32_t count) {
53
     // Reuse the ASM_CYCLES_PER_ITERATION variable to avoid wasting another useless variable
53
     // Reuse the ASM_CYCLES_PER_ITERATION variable to avoid wasting another useless variable
54
-    register uint32_t start = HW_REG(_DWT_CYCCNT) - ASM_CYCLES_PER_ITERATION, elapsed;
54
+    uint32_t start = HW_REG(_DWT_CYCCNT) - ASM_CYCLES_PER_ITERATION, elapsed;
55
     do {
55
     do {
56
       elapsed = HW_REG(_DWT_CYCCNT) - start;
56
       elapsed = HW_REG(_DWT_CYCCNT) - start;
57
     } while (elapsed < count);
57
     } while (elapsed < count);
114
         serialprintPGM(unit);
114
         serialprintPGM(unit);
115
         SERIAL_ECHOLNPAIR(" took: ", total);
115
         SERIAL_ECHOLNPAIR(" took: ", total);
116
         serialprintPGM(unit);
116
         serialprintPGM(unit);
117
-        if (do_flush) SERIAL_FLUSH();
117
+        if (do_flush) SERIAL_FLUSHTX();
118
       };
118
       };
119
 
119
 
120
       uint32_t s, e;
120
       uint32_t s, e;

+ 0
- 1
Marlin/src/core/serial.h View File

58
 //
58
 //
59
 // Serial redirection
59
 // Serial redirection
60
 //
60
 //
61
-typedef int8_t serial_index_t;
62
 #define SERIAL_ALL 0x7F
61
 #define SERIAL_ALL 0x7F
63
 #if HAS_MULTI_SERIAL
62
 #if HAS_MULTI_SERIAL
64
   #define _PORT_REDIRECT(n,p)   REMEMBER(n,multiSerial.portMask,p)
63
   #define _PORT_REDIRECT(n,p)   REMEMBER(n,multiSerial.portMask,p)

+ 1
- 1
Marlin/src/core/serial_base.h View File

79
   void end()                        { static_cast<Child*>(this)->end(); }
79
   void end()                        { static_cast<Child*>(this)->end(); }
80
   /** Check for available data from the port
80
   /** Check for available data from the port
81
       @param index  The port index, usually 0 */
81
       @param index  The port index, usually 0 */
82
-  bool available(uint8_t index = 0) { return static_cast<Child*>(this)->available(index); }
82
+  int available(uint8_t index = 0)  { return static_cast<Child*>(this)->available(index); }
83
   /** Read a value from the port
83
   /** Read a value from the port
84
       @param index  The port index, usually 0 */
84
       @param index  The port index, usually 0 */
85
   int  read(uint8_t index = 0)      { return static_cast<Child*>(this)->read(index); }
85
   int  read(uint8_t index = 0)      { return static_cast<Child*>(this)->read(index); }

+ 43
- 37
Marlin/src/core/serial_hook.h View File

24
 #include "macros.h"
24
 #include "macros.h"
25
 #include "serial_base.h"
25
 #include "serial_base.h"
26
 
26
 
27
+// Used in multiple places
28
+typedef int8_t serial_index_t;
29
+
27
 // 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
30
 // 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
28
 template <class SerialT>
31
 template <class SerialT>
29
 struct BaseSerial : public SerialBase< BaseSerial<SerialT> >, public SerialT {
32
 struct BaseSerial : public SerialBase< BaseSerial<SerialT> >, public SerialT {
35
 
38
 
36
   void msgDone() {}
39
   void msgDone() {}
37
 
40
 
38
-  bool available(uint8_t index) { return index == 0 && SerialT::available(); }
39
-  int read(uint8_t index)       { return index == 0 ? SerialT::read() : -1; }
40
-  bool connected()              { return CALL_IF_EXISTS(bool, static_cast<SerialT*>(this), connected);; }
41
-  void flushTX()                { CALL_IF_EXISTS(void, static_cast<SerialT*>(this), flushTX); }
41
+  // We don't care about indices here, since if one can call us, it's the right index anyway
42
+  int available(uint8_t)  { return (int)SerialT::available(); }
43
+  int read(uint8_t)       { return (int)SerialT::read(); }
44
+  bool connected()        { return CALL_IF_EXISTS(bool, static_cast<SerialT*>(this), connected);; }
45
+  void flushTX()          { CALL_IF_EXISTS(void, static_cast<SerialT*>(this), flushTX); }
42
 
46
 
43
   // We have 2 implementation of the same method in both base class, let's say which one we want
47
   // We have 2 implementation of the same method in both base class, let's say which one we want
44
   using SerialT::available;
48
   using SerialT::available;
65
   bool    & condition;
69
   bool    & condition;
66
   SerialT & out;
70
   SerialT & out;
67
   NO_INLINE size_t write(uint8_t c) { if (condition) return out.write(c); return 0; }
71
   NO_INLINE size_t write(uint8_t c) { if (condition) return out.write(c); return 0; }
68
-  void flush()            { if (condition) out.flush();  }
69
-  void begin(long br)     { out.begin(br); }
70
-  void end()              { out.end(); }
72
+  void flush()                      { if (condition) out.flush();  }
73
+  void begin(long br)               { out.begin(br); }
74
+  void end()                        { out.end(); }
71
 
75
 
72
   void msgDone() {}
76
   void msgDone() {}
73
-  bool connected()              { return CALL_IF_EXISTS(bool, &out, connected); }
74
-  void flushTX()                { CALL_IF_EXISTS(void, &out, flushTX); }
77
+  bool connected()          { return CALL_IF_EXISTS(bool, &out, connected); }
78
+  void flushTX()            { CALL_IF_EXISTS(void, &out, flushTX); }
79
+
80
+  int available(uint8_t )   { return (int)out.available(); }
81
+  int read(uint8_t )        { return (int)out.read(); }
82
+  int available()           { return (int)out.available(); }
83
+  int read()                { return (int)out.read(); }
75
 
84
 
76
-  bool available(uint8_t index) { return index == 0 && out.available(); }
77
-  int read(uint8_t index)       { return index == 0 ? out.read() : -1; }
78
-  using BaseClassT::available;
79
-  using BaseClassT::read;
80
 
85
 
81
   ConditionalSerial(bool & conditionVariable, SerialT & out, const bool e) : BaseClassT(e), condition(conditionVariable), out(out) {}
86
   ConditionalSerial(bool & conditionVariable, SerialT & out, const bool e) : BaseClassT(e), condition(conditionVariable), out(out) {}
82
 };
87
 };
97
   bool connected()              { return Private::HasMember_connected<SerialT>::value ? CALL_IF_EXISTS(bool, &out, connected) : (bool)out; }
102
   bool connected()              { return Private::HasMember_connected<SerialT>::value ? CALL_IF_EXISTS(bool, &out, connected) : (bool)out; }
98
   void flushTX()                { CALL_IF_EXISTS(void, &out, flushTX); }
103
   void flushTX()                { CALL_IF_EXISTS(void, &out, flushTX); }
99
 
104
 
100
-  bool available(uint8_t index) { return index == 0 && out.available(); }
101
-  int read(uint8_t index)       { return index == 0 ? out.read() : -1; }
102
-  bool available()              { return out.available(); }
103
-  int read()                    { return out.read(); }
105
+  int available(uint8_t)        { return (int)out.available(); }
106
+  int read(uint8_t)             { return (int)out.read(); }
107
+  int available()               { return (int)out.available(); }
108
+  int read()                    { return (int)out.read(); }
104
 
109
 
105
   ForwardSerial(const bool e, SerialT & out) : BaseClassT(e), out(out) {}
110
   ForwardSerial(const bool e, SerialT & out) : BaseClassT(e), out(out) {}
106
 };
111
 };
125
     if (eofHook) eofHook(userPointer);
130
     if (eofHook) eofHook(userPointer);
126
   }
131
   }
127
 
132
 
128
-  bool available(uint8_t index) { return index == 0 && SerialT::available(); }
129
-  int read(uint8_t index)       { return index == 0 ? SerialT::read() : -1; }
133
+  int available(uint8_t)  { return (int)SerialT::available(); }
134
+  int read(uint8_t)       { return (int)SerialT::read(); }
130
   using SerialT::available;
135
   using SerialT::available;
131
   using SerialT::read;
136
   using SerialT::read;
132
   using SerialT::flush;
137
   using SerialT::flush;
157
 
162
 
158
   // Forward constructor
163
   // Forward constructor
159
   template <typename... Args>
164
   template <typename... Args>
160
-  RuntimeSerial(const bool e, Args... args) : BaseClassT(e), SerialT(args...) {}
165
+  RuntimeSerial(const bool e, Args... args) : BaseClassT(e), SerialT(args...), writeHook(0), eofHook(0), userPointer(0) {}
161
 };
166
 };
162
 
167
 
163
 // A class that's duplicating its output conditionally to 2 serial interface
168
 // A class that's duplicating its output conditionally to 2 serial interface
164
-template <class Serial0T, class Serial1T, const uint8_t offset = 0>
165
-struct MultiSerial : public SerialBase< MultiSerial<Serial0T, Serial1T, offset> > {
166
-  typedef SerialBase< MultiSerial<Serial0T, Serial1T, offset> > BaseClassT;
169
+template <class Serial0T, class Serial1T, const uint8_t offset = 0, const uint8_t step = 1>
170
+struct MultiSerial : public SerialBase< MultiSerial<Serial0T, Serial1T, offset, step> > {
171
+  typedef SerialBase< MultiSerial<Serial0T, Serial1T, offset, step> > BaseClassT;
167
 
172
 
168
   uint8_t    portMask;
173
   uint8_t    portMask;
169
   Serial0T & serial0;
174
   Serial0T & serial0;
170
   Serial1T & serial1;
175
   Serial1T & serial1;
171
 
176
 
172
   enum Masks {
177
   enum Masks {
173
-    FirstOutputMask   =  (1 << offset),
174
-    SecondOutputMask  =  (1 << (offset + 1)),
178
+    UsageMask         =  ((1 << step) - 1), // A bit mask containing as many bits as step
179
+    FirstOutputMask   =  (UsageMask << offset),
180
+    SecondOutputMask  =  (UsageMask << (offset + step)),
175
     AllMask           = FirstOutputMask | SecondOutputMask,
181
     AllMask           = FirstOutputMask | SecondOutputMask,
176
   };
182
   };
177
 
183
 
185
     if (portMask & FirstOutputMask)   serial0.msgDone();
191
     if (portMask & FirstOutputMask)   serial0.msgDone();
186
     if (portMask & SecondOutputMask)  serial1.msgDone();
192
     if (portMask & SecondOutputMask)  serial1.msgDone();
187
   }
193
   }
188
-  bool available(uint8_t index) {
189
-    switch(index) {
190
-      case 0 + offset: return serial0.available();
191
-      case 1 + offset: return serial1.available();
192
-      default: return false;
193
-    }
194
+  int available(uint8_t index) {
195
+    if (index >= 0 + offset && index < step + offset)
196
+      return serial0.available(index);
197
+    else if (index >= step + offset && index < 2 * step + offset)
198
+      return serial1.available(index);
199
+    return false;
194
   }
200
   }
195
-  NO_INLINE int read(uint8_t index) {
196
-    switch(index) {
197
-      case 0 + offset: return serial0.read();
198
-      case 1 + offset: return serial1.read();
199
-      default: return -1;
200
-    }
201
+  int read(uint8_t index) {
202
+    if (index >= 0 + offset && index < step + offset)
203
+      return serial0.read(index);
204
+    else if (index >= step + offset && index < 2 * step + offset)
205
+      return serial1.read(index);
206
+    return -1;
201
   }
207
   }
202
   void begin(const long br) {
208
   void begin(const long br) {
203
     if (portMask & FirstOutputMask)   serial0.begin(br);
209
     if (portMask & FirstOutputMask)   serial0.begin(br);

+ 17
- 19
Marlin/src/gcode/queue.cpp View File

63
 // Frequently used G-code strings
63
 // Frequently used G-code strings
64
 PGMSTR(G28_STR, "G28");
64
 PGMSTR(G28_STR, "G28");
65
 
65
 
66
+#if NO_TIMEOUTS > 0
67
+  static millis_t last_command_time = 0;
68
+#endif
69
+
66
 /**
70
 /**
67
  * GCode line number handling. Hosts may opt to include line numbers when
71
  * GCode line number handling. Hosts may opt to include line numbers when
68
  * sending commands to Marlin, and lines will be checked for sequentiality.
72
  * sending commands to Marlin, and lines will be checked for sequentiality.
288
  *   B<int>  Block queue space remaining
292
  *   B<int>  Block queue space remaining
289
  */
293
  */
290
 void GCodeQueue::ok_to_send() {
294
 void GCodeQueue::ok_to_send() {
295
+  #if NO_TIMEOUTS > 0
296
+    // Start counting from the last command's execution
297
+    last_command_time = millis();
298
+  #endif
291
   #if HAS_MULTI_SERIAL
299
   #if HAS_MULTI_SERIAL
292
     const serial_index_t serial_ind = command_port();
300
     const serial_index_t serial_ind = command_port();
293
     if (serial_ind < 0) return;
301
     if (serial_ind < 0) return;
324
   ok_to_send();
332
   ok_to_send();
325
 }
333
 }
326
 
334
 
327
-inline bool serial_data_available() {
328
-  byte data_available = 0;
329
-  if (MYSERIAL0.available()) data_available++;
330
-  #ifdef SERIAL_PORT_2
331
-    const bool port2_open = TERN1(HAS_ETHERNET, ethernet.have_telnet_client);
332
-    if (port2_open && MYSERIAL1.available()) data_available++;
333
-  #endif
334
-  return data_available > 0;
335
-}
336
 
335
 
337
-inline int read_serial(const uint8_t index) {
338
-  switch (index) {
339
-    case 0: return MYSERIAL0.read();
340
-    case 1: {
341
-      #if HAS_MULTI_SERIAL
342
-        const bool port2_open = TERN1(HAS_ETHERNET, ethernet.have_telnet_client);
343
-        if (port2_open) return MYSERIAL1.read();
344
-      #endif
336
+// Multiserial already handle the dispatch to/from multiple port by itself
337
+inline bool serial_data_available(uint8_t index = SERIAL_ALL) {
338
+  if (index == SERIAL_ALL) {
339
+    for (index = 0; index < NUM_SERIAL; index++) {
340
+      if (SERIAL_IMPL.available(index) > 0) return true;
345
     }
341
     }
346
-    default: return -1;
342
+    return false;
347
   }
343
   }
344
+  return SERIAL_IMPL.available(index) > 0;
348
 }
345
 }
349
 
346
 
347
+inline int read_serial(const uint8_t index) { return SERIAL_IMPL.read(index); }
348
+
350
 void GCodeQueue::gcode_line_error(PGM_P const err, const serial_index_t serial_ind) {
349
 void GCodeQueue::gcode_line_error(PGM_P const err, const serial_index_t serial_ind) {
351
   PORT_REDIRECT(SERIAL_PORTMASK(serial_ind)); // Reply to the serial port that sent the command
350
   PORT_REDIRECT(SERIAL_PORTMASK(serial_ind)); // Reply to the serial port that sent the command
352
   SERIAL_ERROR_START();
351
   SERIAL_ERROR_START();
460
   // If the command buffer is empty for too long,
459
   // If the command buffer is empty for too long,
461
   // send "wait" to indicate Marlin is still waiting.
460
   // send "wait" to indicate Marlin is still waiting.
462
   #if NO_TIMEOUTS > 0
461
   #if NO_TIMEOUTS > 0
463
-    static millis_t last_command_time = 0;
464
     const millis_t ms = millis();
462
     const millis_t ms = millis();
465
     if (length == 0 && !serial_data_available() && ELAPSED(ms, last_command_time + NO_TIMEOUTS)) {
463
     if (length == 0 && !serial_data_available() && ELAPSED(ms, last_command_time + NO_TIMEOUTS)) {
466
       SERIAL_ECHOLNPGM(STR_WAIT);
464
       SERIAL_ECHOLNPGM(STR_WAIT);

+ 14
- 3
docs/Serial.md View File

30
 Since all the types above are using CRTP, it's possible to combine them to get the appropriate functionality.
30
 Since all the types above are using CRTP, it's possible to combine them to get the appropriate functionality.
31
 This is easily done via type definition of the feature.
31
 This is easily done via type definition of the feature.
32
 
32
 
33
-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:
33
+For example, to create a single serial interface with 2 serial outputs (one enabled at runtime and the other switchable):
34
 ```cpp
34
 ```cpp
35
 typedef MultiSerial< RuntimeSerial<Serial>, ConditionalSerial<TelnetClient> > Serial0Type;
35
 typedef MultiSerial< RuntimeSerial<Serial>, ConditionalSerial<TelnetClient> > Serial0Type;
36
 ```
36
 ```
37
 
37
 
38
+To send the same output to 4 serial ports you could nest `MultiSerial` like this:
39
+```cpp
40
+typedef MultiSerial< MultiSerial< BaseSerial<Serial>, BaseSerial<Serial1> >, MultiSerial< BaseSerial<Serial2>, BaseSerial<Serial3>, 2, 1>, 0, 2> Serial0Type;
41
+```
42
+The magical numbers here are the step and offset for computing the serial port. Simplifying the above monster a bit:
43
+```cpp
44
+MS< A = MS<a, b, offset=0, step=1>, B=MS<c, d, offset=2, step=1>, offset=0, step=2>
45
+```
46
+This means that the underlying multiserial A (with output to `a,b`) is available from offset = 0 to offset + step = 1 (default value).
47
+The multiserial B (with output to `c,d`) is available from offset = 2 (the next step from the root multiserial) to offset + step = 3.
48
+In practice, the root multiserial will redirect any index/mask `offset` to `offset + step - 1` to its first leaf, and any index/mask `offset + step` to `offset + 2*step - 1` to its second leaf.
49
+
38
 ## Emergency parser
50
 ## Emergency parser
39
-By default, the serial base interface provide an emergency parser that's only enable for serial classes that support it.
40
-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.
51
+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 types take a first `bool emergencyParserEnabled` argument to their constructor. You must take into account this parameter when defining the actual type used.
41
 
52
 
42
 ## SERIAL macros
53
 ## SERIAL macros
43
 The following macros are defined (in `serial.h`) to output data to the serial ports:
54
 The following macros are defined (in `serial.h`) to output data to the serial ports:

Loading…
Cancel
Save