RS485 support with G-code M485 (#25680)

Co-authored-by: Stephen Hawes <sphawes@gmail.com>
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
This commit is contained in:
Justin Nesselrotte 2024-07-09 16:00:14 -06:00 committed by GitHub
parent 8b81aae870
commit 44c2682168
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 292 additions and 9 deletions

View File

@ -112,6 +112,16 @@
//#define SERIAL_PORT_3 1
//#define BAUDRATE_3 250000 // :[2400, 9600, 19200, 38400, 57600, 115200, 250000, 500000, 1000000] Enable to override BAUDRATE
/**
* Select a serial port to communicate with RS485 protocol
* :[-1, 0, 1, 2, 3, 4, 5, 6, 7]
*/
//#define RS485_SERIAL_PORT 1
#ifdef RS485_SERIAL_PORT
//#define M485_PROTOCOL 1 // Check your host for protocol compatibility
//#define RS485_BUS_BUFFER_SIZE 128
#endif
// Enable the Bluetooth serial interface on AT90USB devices
//#define BLUETOOTH

View File

@ -117,6 +117,14 @@
#endif
#endif
#ifdef RS485_SERIAL_PORT
#if WITHIN(RS485_SERIAL_PORT, 1, 9)
#define RS485_SERIAL MSERIAL(RS485_SERIAL_PORT)
#else
#error "RS485_SERIAL_PORT must be from 1 to 9."
#endif
#endif
/**
* TODO: review this to return 1 for pins that are not analog input
*/

View File

@ -143,6 +143,17 @@
#endif
#endif
#ifdef RS485_SERIAL_PORT
#if RS485_SERIAL_PORT == -1
#define RS485_SERIAL UsbSerial
#elif WITHIN(RS485_SERIAL_PORT, 1, NUM_UARTS)
#define RS485_SERIAL MSERIAL(RS485_SERIAL_PORT)
#else
#define RS485_SERIAL MSERIAL(1) // dummy port
static_assert(false, "RS485_SERIAL_PORT must be from 1 to " STRINGIFY(NUM_UARTS) ".")
#endif
#endif
/**
* TODO: review this to return 1 for pins that are not analog input
*/

View File

@ -261,6 +261,10 @@
#include "tests/marlin_tests.h"
#endif
#if HAS_RS485_SERIAL
#include "feature/rs485.h"
#endif
PGMSTR(M112_KILL_STR, "M112 Shutdown");
MarlinState marlin_state = MarlinState::MF_INITIALIZING;
@ -1642,6 +1646,10 @@ void setup() {
SETUP_RUN(bdl.init(I2C_BD_SDA_PIN, I2C_BD_SCL_PIN, I2C_BD_DELAY));
#endif
#if HAS_RS485_SERIAL
SETUP_RUN(rs485_init());
#endif
#if ENABLED(FT_MOTION)
SETUP_RUN(ftMotion.init());
#endif

View File

@ -201,7 +201,7 @@ void Power::power_off() {
/**
* Check all conditions that would signal power needing to be on.
*
* @returns bool if power is needed
* @return bool if power is needed
*/
bool Power::is_power_needed() {

View File

@ -0,0 +1,39 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2024 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/>.
*
*/
#include "../inc/MarlinConfig.h"
#if HAS_RS485_SERIAL
#include "rs485.h"
HardwareSerialBusIO rs485BusIO(&RS485_SERIAL);
RS485Bus<RS485_BUS_BUFFER_SIZE> rs485Bus(rs485BusIO, RS485_RX_ENABLE_PIN, RS485_TX_ENABLE_PIN);
PhotonProtocol rs485Protocol;
Packetizer rs485Packetizer(rs485Bus, rs485Protocol);
uint8_t rs485Buffer[RS485_SEND_BUFFER_SIZE];
void rs485_init() { RS485_SERIAL.begin(57600); }
#endif // HAS_RS485_SERIAL

View File

@ -0,0 +1,40 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2024 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"
#include <rs485/rs485bus.hpp>
#include <rs485/bus_adapters/hardware_serial.h>
#include <rs485/protocols/photon.h>
#include <rs485/packetizer.h>
#define RS485_SEND_BUFFER_SIZE 32
extern HardwareSerialBusIO rs485BusIO;
extern RS485Bus<RS485_BUS_BUFFER_SIZE> rs485Bus;
extern PhotonProtocol rs485Protocol;
extern Packetizer rs485Packetizer;
extern uint8_t rs485Buffer[RS485_SEND_BUFFER_SIZE];
void rs485_init();

View File

@ -0,0 +1,127 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2024 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/>.
*
*/
#include "../../../inc/MarlinConfig.h"
#if HAS_RS485_SERIAL
#include "../../../feature/rs485.h"
#include "../../gcode.h"
void write_packet_data() {
Packet packet = rs485Packetizer.getPacket();
for (size_t i = packet.startIndex; i <= packet.endIndex; i++) {
const uint8_t data = rs485Bus[i];
if (data < 0x10) SERIAL_ECHOPGM_P(PSTR("0"));
SERIAL_PRINT(data, PrintBase::Hex);
}
SERIAL_EOL();
}
static void rs485_write_failed(const PacketWriteResult writeResult) {
SERIAL_ERROR_START();
SERIAL_ECHOPGM("RS485: Write failed ");
switch (writeResult) {
case PacketWriteResult::FAILED_INTERRUPTED: SERIAL_ECHOPGM("interrupted"); break;
case PacketWriteResult::FAILED_BUFFER_FULL: SERIAL_ECHOPGM("buffer full"); break;
case PacketWriteResult::FAILED_TIMEOUT: SERIAL_ECHOPGM("timeout"); break;
}
SERIAL_EOL();
}
void GcodeSuite::M485() {
if (strlen(parser.string_arg) & 1) {
SERIAL_ERROR_MSG("String must contain an even number of bytes.");
return;
}
if (strlen(parser.string_arg) > RS485_SEND_BUFFER_SIZE * 2) {
SERIAL_ERROR_MSG("String too long (" STRINGIFY(RS485_SEND_BUFFER_SIZE) " bytes max).");
return;
}
// Convert the string to bytes in the buffer
for (size_t i = 0; i < strlen(parser.string_arg); i += 2) {
const uint8_t nybble1 = HEXCHR(parser.string_arg[i]),
nybble2 = HEXCHR(parser.string_arg[i + 1]);
if (nybble1 == -1 || nybble2 == -1) {
SERIAL_ERROR_START();
SERIAL_ECHOPGM("Not a hex character: ");
SERIAL_CHAR(nybble1 == -1 ? parser.string_arg[i] : parser.string_arg[i+1]);
SERIAL_EOL();
return;
}
rs485Buffer[i >> 1] = (nybble1 & 0x0F) << 4 | (nybble2 & 0x0F);
}
rs485Packetizer.setMaxReadTimeout(50000); // This can be super small since ideally any packets will already be in our buffer
rs485Packetizer.setFalsePacketVerificationTimeout(5000);
// Read and ignore any packets that may have come in, before we write.
while (rs485Packetizer.hasPacket()) {
#if M485_PROTOCOL >= 2
SERIAL_ECHO_START();
#endif
SERIAL_ECHO(F("rs485-"), F("unexpected-packet: "));
write_packet_data();
rs485Packetizer.clearPacket();
}
const PacketWriteResult writeResult = rs485Packetizer.writePacket(rs485Buffer, strlen(parser.string_arg) / 2);
switch (writeResult) {
default: rs485_write_failed(writeResult);
case PacketWriteResult::OK: break; // Nothing to do
}
//millis_t startTime = millis();
bool hasPacket = rs485Packetizer.hasPacket();
//millis_t endTime = millis();
//#if M485_PROTOCOL >= 2
// SERIAL_ECHO_START();
//#endif
//SERIAL_ECHOLN(F("rs485-"), F("time: "), endTime - startTime);
#if M485_PROTOCOL >= 2
SERIAL_ECHO_START();
#endif
SERIAL_ECHO(F("rs485-"));
if (!hasPacket) {
#if M485_PROTOCOL >= 2
SERIAL_ECHOLN(F("timeout"));
#else
SERIAL_ECHOLN(F("reply: TIMEOUT"));
#endif
return;
}
SERIAL_ECHO(F("reply: "));
write_packet_data();
rs485Packetizer.clearPacket();
}
#endif // HAS_RS485_SERIAL

View File

@ -897,6 +897,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
case 430: M430(); break; // M430: Read the system current (A), voltage (V), and power (W)
#endif
#if HAS_RS485_SERIAL
case 485: M485(); break; // M485: Send RS485 packets
#endif
#if ENABLED(CANCEL_OBJECTS)
case 486: M486(); break; // M486: Identify and cancel objects
#endif

View File

@ -243,6 +243,7 @@
* M425 - Enable/Disable and tune backlash correction. (Requires BACKLASH_COMPENSATION and BACKLASH_GCODE)
* M428 - Set the home_offset based on the current_position. Nearest edge applies. (Disabled by NO_WORKSPACE_OFFSETS or DELTA)
* M430 - Read the system current, voltage, and power (Requires POWER_MONITOR_CURRENT, POWER_MONITOR_VOLTAGE, or POWER_MONITOR_FIXED_VOLTAGE)
* M485 - Send RS485 packets (Requires RS485_SERIAL_PORT)
* M486 - Identify and cancel objects. (Requires CANCEL_OBJECTS)
* M500 - Store parameters in EEPROM. (Requires EEPROM_SETTINGS)
* M501 - Restore parameters from EEPROM. (Requires EEPROM_SETTINGS)
@ -1063,6 +1064,10 @@ private:
static void M430();
#endif
#if HAS_RS485_SERIAL
static void M485();
#endif
#if ENABLED(CANCEL_OBJECTS)
static void M486();
#endif

View File

@ -274,9 +274,12 @@ void GCodeParser::parse(char *p) {
// Only use string_arg for these M codes
if (letter == 'M') switch (codenum) {
TERN_(GCODE_MACROS, case 810 ... 819:)
TERN_(EXPECTED_PRINTER_CHECK, case 16:)
case 23: case 28: case 30: case 117 ... 118: case 928:
TERN_(SDSUPPORT, case 23: case 28: case 30: case 928:)
TERN_(HAS_STATUS_MESSAGE, case 117:)
TERN_(HAS_RS485_SERIAL, case 485:)
TERN_(GCODE_MACROS, case 810 ... 819:)
case 118:
string_arg = unescape_string(p);
return;
default: break;

View File

@ -1669,6 +1669,9 @@
#if SERIAL_PORT == -1 || SERIAL_PORT_2 == -1 || SERIAL_PORT_3 == -1
#define HAS_USB_SERIAL 1
#endif
#ifdef RS485_SERIAL_PORT
#define HAS_RS485_SERIAL 1
#endif
#if SERIAL_PORT_2 == -2
#define HAS_ETHERNET 1
#endif

View File

@ -1827,11 +1827,12 @@
//
// Flag the indexed hardware serial ports in use
#define SERIAL_IN_USE(N) ( (defined(SERIAL_PORT) && N == SERIAL_PORT) \
|| (defined(SERIAL_PORT_2) && N == SERIAL_PORT_2) \
|| (defined(SERIAL_PORT_3) && N == SERIAL_PORT_3) \
|| (defined(MMU2_SERIAL_PORT) && N == MMU2_SERIAL_PORT) \
|| (defined(LCD_SERIAL_PORT) && N == LCD_SERIAL_PORT) )
#define SERIAL_IN_USE(N) ( (defined(SERIAL_PORT) && N == SERIAL_PORT) \
|| (defined(SERIAL_PORT_2) && N == SERIAL_PORT_2) \
|| (defined(SERIAL_PORT_3) && N == SERIAL_PORT_3) \
|| (defined(MMU2_SERIAL_PORT) && N == MMU2_SERIAL_PORT) \
|| (defined(LCD_SERIAL_PORT) && N == LCD_SERIAL_PORT) \
|| (defined(RS485_SERIAL_PORT) && N == RS485_SERIAL_PORT) )
// Flag the named hardware serial ports in use
#define TMC_UART_IS(A,N) (defined(A##_HARDWARE_SERIAL) && (CAT(HW_,A##_HARDWARE_SERIAL) == HW_Serial##N || CAT(HW_,A##_HARDWARE_SERIAL) == HW_MSerial##N))

View File

@ -2919,6 +2919,8 @@ static_assert(NUM_SERVOS <= NUM_SERVO_PLUGS, "NUM_SERVOS (or some servo index) i
#error "MMU2_SERIAL_PORT cannot be the same as SERIAL_PORT_2."
#elif defined(LCD_SERIAL_PORT) && MMU2_SERIAL_PORT == LCD_SERIAL_PORT
#error "MMU2_SERIAL_PORT cannot be the same as LCD_SERIAL_PORT."
#elif defined(RS485_SERIAL_PORT) && MMU2_SERIAL_PORT == RS485_SERIAL_PORT
#error "MMU2_SERIAL_PORT cannot be the same as RS485_SERIAL_PORT."
#endif
#endif
@ -2930,6 +2932,8 @@ static_assert(NUM_SERVOS <= NUM_SERVO_PLUGS, "NUM_SERVOS (or some servo index) i
#error "LCD_SERIAL_PORT cannot be the same as SERIAL_PORT."
#elif defined(SERIAL_PORT_2) && LCD_SERIAL_PORT == SERIAL_PORT_2
#error "LCD_SERIAL_PORT cannot be the same as SERIAL_PORT_2."
#elif defined(RS485_SERIAL_PORT) && LCD_SERIAL_PORT == RS485_SERIAL_PORT
#error "LCD_SERIAL_PORT cannot be the same as RS485_SERIAL_PORT."
#endif
#else
#if HAS_DGUS_LCD
@ -2943,6 +2947,17 @@ static_assert(NUM_SERVOS <= NUM_SERVO_PLUGS, "NUM_SERVOS (or some servo index) i
#endif
#endif
/**
* RS485 bus requires a dedicated serial port
*/
#ifdef RS485_SERIAL_PORT
#if RS485_SERIAL_PORT == SERIAL_PORT
#error "RS485_SERIAL_PORT cannot be the same as SERIAL_PORT."
#elif defined(SERIAL_PORT_2) && RS485_SERIAL_PORT == SERIAL_PORT_2
#error "RS485_SERIAL_PORT cannot be the same as SERIAL_PORT_2."
#endif
#endif
/**
* Check existing CS pins against enabled TMC SPI drivers.
*/

View File

@ -206,3 +206,6 @@
#define INDEX_AUX3_PWM2 PB9
#define INDEX_AUX3_A1 PA0
#define INDEX_AUX3_A2 PA1
#define RS485_TX_ENABLE_PIN PD11
#define RS485_RX_ENABLE_PIN PD12

View File

@ -206,3 +206,6 @@
#define LUMEN_AUX3_PWM2 PB9
#define LUMEN_AUX3_A1 PA0
#define LUMEN_AUX3_A2 PA1
#define RS485_TX_ENABLE_PIN PD11
#define RS485_RX_ENABLE_PIN PD12

View File

@ -8,6 +8,7 @@ set -e
use_example_configs Opulo/Lumen_REV3
opt_disable TMC_DEBUG
opt_set RS485_SERIAL_PORT 2 RS485_BUS_BUFFER_SIZE 128
exec_test $1 $2 "Opulo Lumen REV3 Pick-and-Place" "$3"
# cleanup

View File

@ -23,7 +23,6 @@ MKS_WIFI_MODULE = QRCode=https://github.com/makerbase-mks
HAS_TRINAMIC_CONFIG = TMCStepper@~0.7.3
build_src_filter=+<src/module/stepper/trinamic.cpp> +<src/gcode/feature/trinamic/M122.cpp> +<src/gcode/feature/trinamic/M906.cpp> +<src/gcode/feature/trinamic/M911-M914.cpp> +<src/gcode/feature/trinamic/M919.cpp>
HAS_T(RINAMIC_CONFIG|MC_SPI) = build_src_filter=+<src/feature/tmc_util.cpp>
HAS_STEALTHCHOP = build_src_filter=+<src/gcode/feature/trinamic/M569.cpp>
SR_LCD_3W_NL = SailfishLCD=https://github.com/mikeshub/SailfishLCD/archive/6f53c19a8a.zip
HAS_MOTOR_CURRENT_(I2C|DAC|SPI|PWM) = build_src_filter=+<src/gcode/feature/digipot>
HAS_MOTOR_CURRENT_I2C = SlowSoftI2CMaster
@ -317,6 +316,7 @@ NONLINEAR_EXTRUSION = build_src_filter=+<src/gcode/feature/no
HAS_SAVED_POSITIONS = build_src_filter=+<src/gcode/feature/pause/G60.cpp> +<src/gcode/feature/pause/G61.cpp>
PARK_HEAD_ON_PAUSE = build_src_filter=+<src/gcode/feature/pause/M125.cpp>
FILAMENT_LOAD_UNLOAD_GCODES = build_src_filter=+<src/gcode/feature/pause/M701_M702.cpp>
HAS_STEALTHCHOP = build_src_filter=+<src/gcode/feature/trinamic/M569.cpp>
CNC_WORKSPACE_PLANES = build_src_filter=+<src/gcode/geometry/G17-G19.cpp>
CNC_COORDINATE_SYSTEMS = build_src_filter=+<src/gcode/geometry/G53-G59.cpp>
HAS_HOME_OFFSET = build_src_filter=+<src/gcode/geometry/M206_M428.cpp>
@ -334,6 +334,8 @@ HAS_LCD_CONTRAST = build_src_filter=+<src/gcode/lcd/M250.c
EDITABLE_DISPLAY_TIMEOUT = build_src_filter=+<src/gcode/lcd/M255.cpp>
HAS_LCD_BRIGHTNESS = build_src_filter=+<src/gcode/lcd/M256.cpp>
HAS_SOUND = build_src_filter=+<src/gcode/lcd/M300.cpp>
HAS_RS485_SERIAL = jnesselr/rs485@^0.0.9
build_src_filter=+<src/gcode/feature/rs485> +<src/feature/rs485.cpp>
HAS_MULTI_LANGUAGE = build_src_filter=+<src/gcode/lcd/M414.cpp>
TOUCH_SCREEN_CALIBRATION = build_src_filter=+<src/gcode/lcd/M995.cpp>
ARC_SUPPORT = build_src_filter=+<src/gcode/motion/G2_G3.cpp>