Combined LPC / Serial fixes (#21178)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
This commit is contained in:
parent
7cb286bb6e
commit
73332f4df1
|
@ -92,12 +92,11 @@ int MarlinSerialUSB::read() {
|
|||
return c;
|
||||
}
|
||||
|
||||
bool MarlinSerialUSB::available() {
|
||||
/* If Pending chars */
|
||||
return pending_char >= 0 ||
|
||||
/* or USB CDC enumerated and configured on the PC side and some
|
||||
bytes where sent to us */
|
||||
(usb_task_cdc_isenabled() && udi_cdc_is_rx_ready());
|
||||
int MarlinSerialUSB::available() {
|
||||
if (pending_char > 0) return pending_char;
|
||||
return pending_char == 0 ||
|
||||
// or USB CDC enumerated and configured on the PC side and some bytes where sent to us */
|
||||
(usb_task_cdc_isenabled() && udi_cdc_is_rx_ready());
|
||||
}
|
||||
|
||||
void MarlinSerialUSB::flush() { }
|
||||
|
|
|
@ -39,7 +39,7 @@ struct MarlinSerialUSB {
|
|||
int peek();
|
||||
int read();
|
||||
void flush();
|
||||
bool available();
|
||||
int available();
|
||||
size_t write(const uint8_t c);
|
||||
|
||||
#if ENABLED(SERIAL_STATS_DROPPED_RX)
|
||||
|
|
|
@ -25,20 +25,46 @@
|
|||
#include "MarlinSerial.h"
|
||||
|
||||
#if ANY_SERIAL_IS(0)
|
||||
MSerialT MSerial(true, LPC_UART0);
|
||||
extern "C" void UART0_IRQHandler() { MSerial.IRQHandler(); }
|
||||
MarlinSerial _MSerial(LPC_UART0);
|
||||
MSerialT MSerial(true, _MSerial);
|
||||
extern "C" void UART0_IRQHandler() { _MSerial.IRQHandler(); }
|
||||
#endif
|
||||
#if ANY_SERIAL_IS(1)
|
||||
MSerialT MSerial1(true, (LPC_UART_TypeDef *) LPC_UART1);
|
||||
extern "C" void UART1_IRQHandler() { MSerial1.IRQHandler(); }
|
||||
MarlinSerial _MSerial1((LPC_UART_TypeDef *) LPC_UART1);
|
||||
MSerialT MSerial1(true, _MSerial1);
|
||||
extern "C" void UART1_IRQHandler() { _MSerial1.IRQHandler(); }
|
||||
#endif
|
||||
#if ANY_SERIAL_IS(2)
|
||||
MSerialT MSerial2(true, LPC_UART2);
|
||||
extern "C" void UART2_IRQHandler() { MSerial2.IRQHandler(); }
|
||||
MarlinSerial _MSerial2(LPC_UART2);
|
||||
MSerialT MSerial2(true, _MSerial2);
|
||||
extern "C" void UART2_IRQHandler() { _MSerial2.IRQHandler(); }
|
||||
#endif
|
||||
#if ANY_SERIAL_IS(3)
|
||||
MSerialT MSerial3(true, LPC_UART3);
|
||||
extern "C" void UART3_IRQHandler() { MSerial3.IRQHandler(); }
|
||||
MarlinSerial _MSerial3(LPC_UART3);
|
||||
MSerialT MSerial3(true, _MSerial3);
|
||||
extern "C" void UART3_IRQHandler() { _MSerial3.IRQHandler(); }
|
||||
#endif
|
||||
|
||||
#if ENABLED(EMERGENCY_PARSER)
|
||||
|
||||
bool MarlinSerial::recv_callback(const char c) {
|
||||
// Need to figure out which serial port we are and react in consequence (Marlin does not have CONTAINER_OF macro)
|
||||
if (false) {}
|
||||
#if ANY_SERIAL_IS(0)
|
||||
else if (this == &_MSerial) emergency_parser.update(MSerial.emergency_state, c);
|
||||
#endif
|
||||
#if ANY_SERIAL_IS(1)
|
||||
else if (this == &_MSerial1) emergency_parser.update(MSerial1.emergency_state, c);
|
||||
#endif
|
||||
#if ANY_SERIAL_IS(2)
|
||||
else if (this == &_MSerial2) emergency_parser.update(MSerial2.emergency_state, c);
|
||||
#endif
|
||||
#if ANY_SERIAL_IS(3)
|
||||
else if (this == &_MSerial3) emergency_parser.update(MSerial3.emergency_state, c);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif // TARGET_LPC1768
|
||||
|
|
|
@ -47,15 +47,21 @@ public:
|
|||
void end() {}
|
||||
|
||||
#if ENABLED(EMERGENCY_PARSER)
|
||||
bool recv_callback(const char c) override {
|
||||
emergency_parser.update(static_cast<Serial0Type<MarlinSerial> *>(this)->emergency_state, c);
|
||||
return true; // do not discard character
|
||||
}
|
||||
bool recv_callback(const char c) override;
|
||||
#endif
|
||||
};
|
||||
|
||||
typedef Serial0Type<MarlinSerial> MSerialT;
|
||||
// On LPC176x framework, HardwareSerial does not implement the same interface as Arduino's Serial, so overloads
|
||||
// of 'available' and 'read' method are not used in this multiple inheritance scenario.
|
||||
// Instead, use a ForwardSerial here that adapts the interface.
|
||||
typedef ForwardSerial0Type<MarlinSerial> MSerialT;
|
||||
extern MSerialT MSerial;
|
||||
extern MSerialT MSerial1;
|
||||
extern MSerialT MSerial2;
|
||||
extern MSerialT MSerial3;
|
||||
|
||||
// Consequently, we can't use a RuntimeSerial either. The workaround would be to use a RuntimeSerial<ForwardSerial<MarlinSerial>> type here
|
||||
// Right now, let's ignore this until it's actually required.
|
||||
#if ENABLED(SERIAL_RUNTIME_HOOK)
|
||||
#error "SERIAL_RUNTIME_HOOK is not yet supported for LPC176x."
|
||||
#endif
|
||||
|
|
|
@ -29,8 +29,8 @@
|
|||
|
||||
EmergencyParser::State emergency_state;
|
||||
|
||||
bool CDC_RecvCallback(const char buffer) {
|
||||
emergency_parser.update(emergency_state, buffer);
|
||||
bool CDC_RecvCallback(const char c) {
|
||||
emergency_parser.update(emergency_state, c);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
// Use hardware cycle counter instead, it's much safer
|
||||
void delay_dwt(uint32_t count) {
|
||||
// Reuse the ASM_CYCLES_PER_ITERATION variable to avoid wasting another useless variable
|
||||
register uint32_t start = HW_REG(_DWT_CYCCNT) - ASM_CYCLES_PER_ITERATION, elapsed;
|
||||
uint32_t start = HW_REG(_DWT_CYCCNT) - ASM_CYCLES_PER_ITERATION, elapsed;
|
||||
do {
|
||||
elapsed = HW_REG(_DWT_CYCCNT) - start;
|
||||
} while (elapsed < count);
|
||||
|
@ -114,7 +114,7 @@
|
|||
serialprintPGM(unit);
|
||||
SERIAL_ECHOLNPAIR(" took: ", total);
|
||||
serialprintPGM(unit);
|
||||
if (do_flush) SERIAL_FLUSH();
|
||||
if (do_flush) SERIAL_FLUSHTX();
|
||||
};
|
||||
|
||||
uint32_t s, e;
|
||||
|
|
|
@ -58,7 +58,6 @@ extern uint8_t marlin_debug_flags;
|
|||
//
|
||||
// Serial redirection
|
||||
//
|
||||
typedef int8_t serial_index_t;
|
||||
#define SERIAL_ALL 0x7F
|
||||
#if HAS_MULTI_SERIAL
|
||||
#define _PORT_REDIRECT(n,p) REMEMBER(n,multiSerial.portMask,p)
|
||||
|
|
|
@ -79,7 +79,7 @@ struct SerialBase {
|
|||
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); }
|
||||
int 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); }
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
#include "macros.h"
|
||||
#include "serial_base.h"
|
||||
|
||||
// Used in multiple places
|
||||
typedef int8_t serial_index_t;
|
||||
|
||||
// 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 {
|
||||
|
@ -35,10 +38,11 @@ struct BaseSerial : public SerialBase< BaseSerial<SerialT> >, public SerialT {
|
|||
|
||||
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);; }
|
||||
void flushTX() { CALL_IF_EXISTS(void, static_cast<SerialT*>(this), flushTX); }
|
||||
// We don't care about indices here, since if one can call us, it's the right index anyway
|
||||
int available(uint8_t) { return (int)SerialT::available(); }
|
||||
int read(uint8_t) { return (int)SerialT::read(); }
|
||||
bool connected() { return CALL_IF_EXISTS(bool, static_cast<SerialT*>(this), connected);; }
|
||||
void flushTX() { CALL_IF_EXISTS(void, static_cast<SerialT*>(this), flushTX); }
|
||||
|
||||
// We have 2 implementation of the same method in both base class, let's say which one we want
|
||||
using SerialT::available;
|
||||
|
@ -65,18 +69,19 @@ struct ConditionalSerial : public SerialBase< ConditionalSerial<SerialT> > {
|
|||
bool & condition;
|
||||
SerialT & out;
|
||||
NO_INLINE 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 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); }
|
||||
void flushTX() { CALL_IF_EXISTS(void, &out, flushTX); }
|
||||
bool connected() { return CALL_IF_EXISTS(bool, &out, connected); }
|
||||
void flushTX() { CALL_IF_EXISTS(void, &out, flushTX); }
|
||||
|
||||
int available(uint8_t ) { return (int)out.available(); }
|
||||
int read(uint8_t ) { return (int)out.read(); }
|
||||
int available() { return (int)out.available(); }
|
||||
int read() { return (int)out.read(); }
|
||||
|
||||
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) {}
|
||||
};
|
||||
|
@ -97,10 +102,10 @@ struct ForwardSerial : public SerialBase< ForwardSerial<SerialT> > {
|
|||
bool connected() { return Private::HasMember_connected<SerialT>::value ? CALL_IF_EXISTS(bool, &out, connected) : (bool)out; }
|
||||
void flushTX() { CALL_IF_EXISTS(void, &out, flushTX); }
|
||||
|
||||
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(); }
|
||||
int available(uint8_t) { return (int)out.available(); }
|
||||
int read(uint8_t) { return (int)out.read(); }
|
||||
int available() { return (int)out.available(); }
|
||||
int read() { return (int)out.read(); }
|
||||
|
||||
ForwardSerial(const bool e, SerialT & out) : BaseClassT(e), out(out) {}
|
||||
};
|
||||
|
@ -125,8 +130,8 @@ struct RuntimeSerial : public SerialBase< RuntimeSerial<SerialT> >, public Seria
|
|||
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; }
|
||||
int available(uint8_t) { return (int)SerialT::available(); }
|
||||
int read(uint8_t) { return (int)SerialT::read(); }
|
||||
using SerialT::available;
|
||||
using SerialT::read;
|
||||
using SerialT::flush;
|
||||
|
@ -157,21 +162,22 @@ struct RuntimeSerial : public SerialBase< RuntimeSerial<SerialT> >, public Seria
|
|||
|
||||
// Forward constructor
|
||||
template <typename... Args>
|
||||
RuntimeSerial(const bool e, Args... args) : BaseClassT(e), SerialT(args...) {}
|
||||
RuntimeSerial(const bool e, Args... args) : BaseClassT(e), SerialT(args...), writeHook(0), eofHook(0), userPointer(0) {}
|
||||
};
|
||||
|
||||
// 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;
|
||||
template <class Serial0T, class Serial1T, const uint8_t offset = 0, const uint8_t step = 1>
|
||||
struct MultiSerial : public SerialBase< MultiSerial<Serial0T, Serial1T, offset, step> > {
|
||||
typedef SerialBase< MultiSerial<Serial0T, Serial1T, offset, step> > BaseClassT;
|
||||
|
||||
uint8_t portMask;
|
||||
Serial0T & serial0;
|
||||
Serial1T & serial1;
|
||||
|
||||
enum Masks {
|
||||
FirstOutputMask = (1 << offset),
|
||||
SecondOutputMask = (1 << (offset + 1)),
|
||||
UsageMask = ((1 << step) - 1), // A bit mask containing as many bits as step
|
||||
FirstOutputMask = (UsageMask << offset),
|
||||
SecondOutputMask = (UsageMask << (offset + step)),
|
||||
AllMask = FirstOutputMask | SecondOutputMask,
|
||||
};
|
||||
|
||||
|
@ -185,19 +191,19 @@ struct MultiSerial : public SerialBase< MultiSerial<Serial0T, Serial1T, offset>
|
|||
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 available(uint8_t index) {
|
||||
if (index >= 0 + offset && index < step + offset)
|
||||
return serial0.available(index);
|
||||
else if (index >= step + offset && index < 2 * step + offset)
|
||||
return serial1.available(index);
|
||||
return false;
|
||||
}
|
||||
NO_INLINE int read(uint8_t index) {
|
||||
switch(index) {
|
||||
case 0 + offset: return serial0.read();
|
||||
case 1 + offset: return serial1.read();
|
||||
default: return -1;
|
||||
}
|
||||
int read(uint8_t index) {
|
||||
if (index >= 0 + offset && index < step + offset)
|
||||
return serial0.read(index);
|
||||
else if (index >= step + offset && index < 2 * step + offset)
|
||||
return serial1.read(index);
|
||||
return -1;
|
||||
}
|
||||
void begin(const long br) {
|
||||
if (portMask & FirstOutputMask) serial0.begin(br);
|
||||
|
|
|
@ -63,6 +63,10 @@ GCodeQueue queue;
|
|||
// Frequently used G-code strings
|
||||
PGMSTR(G28_STR, "G28");
|
||||
|
||||
#if NO_TIMEOUTS > 0
|
||||
static millis_t last_command_time = 0;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* GCode line number handling. Hosts may opt to include line numbers when
|
||||
* sending commands to Marlin, and lines will be checked for sequentiality.
|
||||
|
@ -288,6 +292,10 @@ void GCodeQueue::enqueue_now_P(PGM_P const pgcode) {
|
|||
* B<int> Block queue space remaining
|
||||
*/
|
||||
void GCodeQueue::ok_to_send() {
|
||||
#if NO_TIMEOUTS > 0
|
||||
// Start counting from the last command's execution
|
||||
last_command_time = millis();
|
||||
#endif
|
||||
#if HAS_MULTI_SERIAL
|
||||
const serial_index_t serial_ind = command_port();
|
||||
if (serial_ind < 0) return;
|
||||
|
@ -324,28 +332,19 @@ void GCodeQueue::flush_and_request_resend() {
|
|||
ok_to_send();
|
||||
}
|
||||
|
||||
inline bool serial_data_available() {
|
||||
byte data_available = 0;
|
||||
if (MYSERIAL0.available()) data_available++;
|
||||
#ifdef SERIAL_PORT_2
|
||||
const bool port2_open = TERN1(HAS_ETHERNET, ethernet.have_telnet_client);
|
||||
if (port2_open && MYSERIAL1.available()) data_available++;
|
||||
#endif
|
||||
return data_available > 0;
|
||||
|
||||
// Multiserial already handle the dispatch to/from multiple port by itself
|
||||
inline bool serial_data_available(uint8_t index = SERIAL_ALL) {
|
||||
if (index == SERIAL_ALL) {
|
||||
for (index = 0; index < NUM_SERIAL; index++) {
|
||||
if (SERIAL_IMPL.available(index) > 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return SERIAL_IMPL.available(index) > 0;
|
||||
}
|
||||
|
||||
inline int read_serial(const uint8_t index) {
|
||||
switch (index) {
|
||||
case 0: return MYSERIAL0.read();
|
||||
case 1: {
|
||||
#if HAS_MULTI_SERIAL
|
||||
const bool port2_open = TERN1(HAS_ETHERNET, ethernet.have_telnet_client);
|
||||
if (port2_open) return MYSERIAL1.read();
|
||||
#endif
|
||||
}
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
inline int read_serial(const uint8_t index) { return SERIAL_IMPL.read(index); }
|
||||
|
||||
void GCodeQueue::gcode_line_error(PGM_P const err, const serial_index_t serial_ind) {
|
||||
PORT_REDIRECT(SERIAL_PORTMASK(serial_ind)); // Reply to the serial port that sent the command
|
||||
|
@ -460,7 +459,6 @@ void GCodeQueue::get_serial_commands() {
|
|||
// If the command buffer is empty for too long,
|
||||
// send "wait" to indicate Marlin is still waiting.
|
||||
#if NO_TIMEOUTS > 0
|
||||
static millis_t last_command_time = 0;
|
||||
const millis_t ms = millis();
|
||||
if (length == 0 && !serial_data_available() && ELAPSED(ms, last_command_time + NO_TIMEOUTS)) {
|
||||
SERIAL_ECHOLNPGM(STR_WAIT);
|
||||
|
|
|
@ -30,14 +30,25 @@ In the `Marlin/src/core/serial_hook.h` file, the different serial feature are de
|
|||
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:
|
||||
For example, to create a single serial interface with 2 serial outputs (one enabled at runtime and the other switchable):
|
||||
```cpp
|
||||
typedef MultiSerial< RuntimeSerial<Serial>, ConditionalSerial<TelnetClient> > Serial0Type;
|
||||
```
|
||||
|
||||
To send the same output to 4 serial ports you could nest `MultiSerial` like this:
|
||||
```cpp
|
||||
typedef MultiSerial< MultiSerial< BaseSerial<Serial>, BaseSerial<Serial1> >, MultiSerial< BaseSerial<Serial2>, BaseSerial<Serial3>, 2, 1>, 0, 2> Serial0Type;
|
||||
```
|
||||
The magical numbers here are the step and offset for computing the serial port. Simplifying the above monster a bit:
|
||||
```cpp
|
||||
MS< A = MS<a, b, offset=0, step=1>, B=MS<c, d, offset=2, step=1>, offset=0, step=2>
|
||||
```
|
||||
This means that the underlying multiserial A (with output to `a,b`) is available from offset = 0 to offset + step = 1 (default value).
|
||||
The multiserial B (with output to `c,d`) is available from offset = 2 (the next step from the root multiserial) to offset + step = 3.
|
||||
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.
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
## SERIAL macros
|
||||
The following macros are defined (in `serial.h`) to output data to the serial ports:
|
||||
|
|
Loading…
Reference in a new issue