diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index 770ef0b889f1..837716120d1e 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -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 diff --git a/Marlin/src/HAL/STM32/HAL.h b/Marlin/src/HAL/STM32/HAL.h index 9c74a95ff0c4..e25ca27d8eda 100644 --- a/Marlin/src/HAL/STM32/HAL.h +++ b/Marlin/src/HAL/STM32/HAL.h @@ -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 */ diff --git a/Marlin/src/HAL/STM32F1/HAL.h b/Marlin/src/HAL/STM32F1/HAL.h index 007bf83b0974..c422f60500b6 100644 --- a/Marlin/src/HAL/STM32F1/HAL.h +++ b/Marlin/src/HAL/STM32F1/HAL.h @@ -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 */ diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index dbfb822015c4..9b508b0e9f9b 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -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 diff --git a/Marlin/src/feature/power.cpp b/Marlin/src/feature/power.cpp index 20eb63a6f160..c6dc56283692 100644 --- a/Marlin/src/feature/power.cpp +++ b/Marlin/src/feature/power.cpp @@ -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() { diff --git a/Marlin/src/feature/rs485.cpp b/Marlin/src/feature/rs485.cpp new file mode 100644 index 000000000000..800918545081 --- /dev/null +++ b/Marlin/src/feature/rs485.cpp @@ -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 . + * + */ +#include "../inc/MarlinConfig.h" + +#if HAS_RS485_SERIAL + +#include "rs485.h" + +HardwareSerialBusIO rs485BusIO(&RS485_SERIAL); +RS485Bus 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 diff --git a/Marlin/src/feature/rs485.h b/Marlin/src/feature/rs485.h new file mode 100644 index 000000000000..3327626a3c05 --- /dev/null +++ b/Marlin/src/feature/rs485.h @@ -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 . + * + */ +#pragma once + +#include "../inc/MarlinConfigPre.h" + +#include +#include + +#include +#include + +#define RS485_SEND_BUFFER_SIZE 32 + +extern HardwareSerialBusIO rs485BusIO; +extern RS485Bus rs485Bus; +extern PhotonProtocol rs485Protocol; +extern Packetizer rs485Packetizer; +extern uint8_t rs485Buffer[RS485_SEND_BUFFER_SIZE]; + +void rs485_init(); diff --git a/Marlin/src/gcode/feature/rs485/M485.cpp b/Marlin/src/gcode/feature/rs485/M485.cpp new file mode 100644 index 000000000000..03640a034eec --- /dev/null +++ b/Marlin/src/gcode/feature/rs485/M485.cpp @@ -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 . + * + */ + +#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 diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp index 9e81c7d4bc4b..2a8bfa4e662a 100644 --- a/Marlin/src/gcode/gcode.cpp +++ b/Marlin/src/gcode/gcode.cpp @@ -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 diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h index 144d724cd9cd..bb2f0099949b 100644 --- a/Marlin/src/gcode/gcode.h +++ b/Marlin/src/gcode/gcode.h @@ -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 @@ class GcodeSuite { static void M430(); #endif + #if HAS_RS485_SERIAL + static void M485(); + #endif + #if ENABLED(CANCEL_OBJECTS) static void M486(); #endif diff --git a/Marlin/src/gcode/parser.cpp b/Marlin/src/gcode/parser.cpp index 16c3b2d9bd0b..4b8bbb925fde 100644 --- a/Marlin/src/gcode/parser.cpp +++ b/Marlin/src/gcode/parser.cpp @@ -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; diff --git a/Marlin/src/inc/Conditionals_LCD.h b/Marlin/src/inc/Conditionals_LCD.h index ab5fd9e55cbd..4e5aa0bc0dc4 100644 --- a/Marlin/src/inc/Conditionals_LCD.h +++ b/Marlin/src/inc/Conditionals_LCD.h @@ -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 diff --git a/Marlin/src/inc/Conditionals_post.h b/Marlin/src/inc/Conditionals_post.h index 7af8536d90fe..e6f307877d0f 100644 --- a/Marlin/src/inc/Conditionals_post.h +++ b/Marlin/src/inc/Conditionals_post.h @@ -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)) diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index 7113e9946b47..6b9deef84dc7 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -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. */ diff --git a/Marlin/src/pins/stm32f4/pins_OPULO_LUMEN_REV3.h b/Marlin/src/pins/stm32f4/pins_OPULO_LUMEN_REV3.h index a256d2e7cb26..cf400335f531 100644 --- a/Marlin/src/pins/stm32f4/pins_OPULO_LUMEN_REV3.h +++ b/Marlin/src/pins/stm32f4/pins_OPULO_LUMEN_REV3.h @@ -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 diff --git a/Marlin/src/pins/stm32f4/pins_OPULO_LUMEN_REV4.h b/Marlin/src/pins/stm32f4/pins_OPULO_LUMEN_REV4.h index f7daa4c3ec41..9c374eae4444 100644 --- a/Marlin/src/pins/stm32f4/pins_OPULO_LUMEN_REV4.h +++ b/Marlin/src/pins/stm32f4/pins_OPULO_LUMEN_REV4.h @@ -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 diff --git a/buildroot/tests/Opulo_Lumen_REV3 b/buildroot/tests/Opulo_Lumen_REV3 index f12f69011e78..436a71e64418 100755 --- a/buildroot/tests/Opulo_Lumen_REV3 +++ b/buildroot/tests/Opulo_Lumen_REV3 @@ -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 diff --git a/ini/features.ini b/ini/features.ini index d60bdcc6cf66..375c26ffb782 100644 --- a/ini/features.ini +++ b/ini/features.ini @@ -23,7 +23,6 @@ MKS_WIFI_MODULE = QRCode=https://github.com/makerbase-mks HAS_TRINAMIC_CONFIG = TMCStepper@~0.7.3 build_src_filter=+ + + + + HAS_T(RINAMIC_CONFIG|MC_SPI) = build_src_filter=+ -HAS_STEALTHCHOP = build_src_filter=+ SR_LCD_3W_NL = SailfishLCD=https://github.com/mikeshub/SailfishLCD/archive/6f53c19a8a.zip HAS_MOTOR_CURRENT_(I2C|DAC|SPI|PWM) = build_src_filter=+ HAS_MOTOR_CURRENT_I2C = SlowSoftI2CMaster @@ -317,6 +316,7 @@ NONLINEAR_EXTRUSION = build_src_filter=+ + PARK_HEAD_ON_PAUSE = build_src_filter=+ FILAMENT_LOAD_UNLOAD_GCODES = build_src_filter=+ +HAS_STEALTHCHOP = build_src_filter=+ CNC_WORKSPACE_PLANES = build_src_filter=+ CNC_COORDINATE_SYSTEMS = build_src_filter=+ HAS_HOME_OFFSET = build_src_filter=+ @@ -334,6 +334,8 @@ HAS_LCD_CONTRAST = build_src_filter=+ HAS_LCD_BRIGHTNESS = build_src_filter=+ HAS_SOUND = build_src_filter=+ +HAS_RS485_SERIAL = jnesselr/rs485@^0.0.9 + build_src_filter=+ + HAS_MULTI_LANGUAGE = build_src_filter=+ TOUCH_SCREEN_CALIBRATION = build_src_filter=+ ARC_SUPPORT = build_src_filter=+