Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CAN/FDCAN bus host and toolhead #27547

Open
wants to merge 20 commits into
base: bugfix-2.1.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Marlin/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@
//#define RS485_BUS_BUFFER_SIZE 128
#endif

// Enable CAN bus support and protocol
//#define CAN_HOST
//#define CAN_TOOLHEAD
//#define CAN_DEBUG

// Enable the Bluetooth serial interface on AT90USB devices
//#define BLUETOOTH

Expand Down
852 changes: 852 additions & 0 deletions Marlin/src/HAL/STM32/CAN_host.cpp

Large diffs are not rendered by default.

879 changes: 879 additions & 0 deletions Marlin/src/HAL/STM32/FDCAN_host.cpp

Large diffs are not rendered by default.

743 changes: 743 additions & 0 deletions Marlin/src/HAL/STM32/FDCAN_toolhead.cpp

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions Marlin/src/HAL/STM32/Servo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@

#include "Servo.h"

#if ENABLED(CAN_HOST)
#include "../shared/CAN.h"
#endif

static uint_fast8_t servoCount = 0;
static libServo *servos[NUM_SERVOS] = {0};
constexpr millis_t servoDelay[] = SERVO_DELAY;
Expand Down Expand Up @@ -71,6 +75,18 @@ int8_t libServo::attach(const int pin, const int min, const int max) {
}

void libServo::move(const int value) {

#if ENABLED(CAN_HOST) // Forward direct Servo command to head
constexpr int angles[2] = Z_SERVO_ANGLES;
// Translate M280 S10 to M401, M280 S90 to M402
if (value == angles[0])
CAN_host_send_gcode_2params('M', 401, 0, 0, 0, 0); // Deploy Angle: Send "M401" instead, enables interrupt etc.
else if (value == angles[1])
CAN_host_send_gcode_2params('M', 402, 0, 0, 0, 0); // Stow Angle: Send "M402" instead, enables interrupt etc.
else
CAN_host_send_gcode_2params('M', 280, 'S', value, 'P', 0); // M280 S[value] P0
#endif

if (attach(0) >= 0) {
stm32_servo.write(value);
safe_delay(delay);
Expand Down
4 changes: 2 additions & 2 deletions Marlin/src/HAL/STM32/inc/SanityCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
#error "SDCARD_EEPROM_EMULATION requires SDSUPPORT. Enable SDSUPPORT or choose another EEPROM emulation."
#endif

#if !defined(STM32F4xx) && ENABLED(FLASH_EEPROM_LEVELING)
#error "FLASH_EEPROM_LEVELING is currently only supported on STM32F4 hardware."
#if !ANY(STM32F4xx, STM32H7xx) && ENABLED(FLASH_EEPROM_LEVELING)
#error "FLASH_EEPROM_LEVELING is currently only supported on STM32F4/H7 hardware."
#endif

#if ENABLED(SERIAL_STATS_MAX_RX_QUEUED)
Expand Down
106 changes: 106 additions & 0 deletions Marlin/src/HAL/shared/CAN.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* 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"

#define CAN_HOST_MAX_STRING_MSG_LENGTH 128 // Max string message length to receive from the toolhead
#define CAN_HOST_GCODE_TIME_SYNC_NO 7777 // A unique unused Gcode number to trigger a time sync
#define CAN_HOST_CONFIGURATION_COMPLETE 7778 // Signal the configuration is complete
#define CAN_HOST_MAX_WAIT_TIME 25 // Time in ms to wait for CAN FIFO buffers
#define CAN_HOST_E0_TEMP_UPDATE_WATCHDOG_TIME 3000 // An E0 temp update must be received withing this time
#define CAN_HOST_ERROR_REPEAT_TIME 10000 // Time between report repeats of an error message

#define STDID_FIFO_TOGGLE_BIT 0b10000000000
#define EXTID_FIFO_TOGGLE_BIT 0x10000000

#define CAN_ID_IO_MASK 0b11111 // Mask for the 5 virtual IO bits
#define CAN_ID_GCODE_MASK 0b111111111111111 // Gcode type and number mask
#define CAN_ID_GCODE_NUMBER_MASK 0b1111111111111 // Gcode number mask
#define CAN_ID_PARAMETER_LETTER_MASK 0b11111
#define CAN_ID_PARAMETER_COUNT_MASK 0b111 // Parameter count
#define CAN_ID_GCODE_TYPE_MASK 0b11 // Gcode type mask

#define CAN_ID_PROBE_BIT_MASK (1 << 0) // Virtual IO bit for Z-probe pin
#define CAN_ID_PROBE_BIT_POS 0
#define CAN_ID_FILAMENT_BIT_MASK (1 << 1) // Virtual IO bit for filament pin
#define CAN_ID_FILAMENT_BIT_POS 1
#define CAN_ID_X_ENDSTOP_BIT_MASK (1 << 2) // Virtual IO bit for X-min pin
#define CAN_ID_X_ENDSTOP_BIT_POS 2
#define CAN_ID_Y_ENDSTOP_BIT_MASK (1 << 3) // Virtual IO bit for Y-min pin
#define CAN_ID_Y_ENDSTOP_BIT_POS 3
#define CAN_ID_Z_ENDSTOP_BIT_MASK (1 << 4) // Virtual IO bit for Z-min pin
#define CAN_ID_Z_ENDSTOP_BIT_POS 4
#define CAN_ID_STRING_MESSAGE_BIT_MASK (1 << 5) // Signals the toolhead sent a string message
#define CAN_ID_STRING_MESSAGE_BIT_POS 5
#define CAN_ID_REQUEST_SETUP_BIT_MASK (1 << 6) // Signals the toolhead requests setup information
#define CAN_ID_REQUEST_SETUP_BIT_POS 6
#define CAN_ID_TMC_OT_BIT_MASK (1 << 7) // Signals the toolhead signals a TMC Over-Temp error
#define CAN_ID_TMC_OT_BIT_POS 7
#define CAN_ID_REQUEST_TIME_SYNC_BIT_MASK (1 << 8) // Signals the toolhead requested a time sync
#define CAN_ID_REQUEST_TIME_SYNC_BIT_POS 8
#define CAN_ID_ERROR_BIT_MASK (1 << 9) // Signals the toolhead encountered an error
#define CAN_ID_ERROR_BIT_POS 9

#define CAN_ID_PARAMETER1_BIT_POS 0
#define CAN_ID_PARAMETER2_BIT_POS 5
#define CAN_ID_GCODE_NUMBER_BIT_POS 10
#define CAN_ID_GCODE_TYPE_BIT_POS 23
#define CAN_ID_PARAMETER_COUNT_BIT_POS 25

#define CAN_ID_GCODE_TYPE_D 0
#define CAN_ID_GCODE_TYPE_G 1
#define CAN_ID_GCODE_TYPE_M 2
#define CAN_ID_GCODE_TYPE_T 3

// Host error messages
#define CAN_ERROR_HOST_RX_FIFO_OVERFLOW (1 << 0) // Incoming message lost
#define CAN_ERROR_HOST_TX_MSG_DROPPED (1 << 1) // Outgoing message dropped
#define CAN_ERROR_HOST_INVALID_GCODE (1 << 2) // Gcode could not be sent over CANBUS
#define CAN_ERROR_HOST_INVALID_BAUDRATE (1 << 3) // Generated baudrate doesn't match CAN_BAUDRATE

// Toolhead error messages
#define CAN_ERROR_TOOLHEAD_RX_FIFO_OVERFLOW (1 << 4)
#define CAN_ERROR_TOOLHEAD_TX_FIFO_OVERFLOW (1 << 5)
#define CAN_ERROR_TOOLHEAD_INCOMPLETE_GCODE_RECEIVED (1 << 6)
#define CAN_ERROR_TOOLHEAD_MARLIN_CMD_BUFFER_OVERFLOW (1 << 7)
#define CAN_ERROR_TOOLHEAD_INVALID_BAUDRATE (1 << 8) // Generated baudrate doesn't match CAN_BAUDRATE

// CAN error messsages
#define CAN_ERROR_MSG_RX_FIFO_OVERFLOW "CAN RX FIFO overflow"
#define CAN_ERROR_MSG_TX_FIFO_OVERFLOW "CAN TX FIFO overflow"
#define CAN_ERROR_MSG_INCOMPLETE_GCODE "Incomplete Gcode message received"
#define CAN_ERROR_MSG_MARLIN_CMM_BUF_OVERFLOW "Marlin CMD buffer overflow"
#define CAN_ERROR_MSG_INVALID_BAUDRATE "Incorrect CAN baudrate"

void CAN_host_idle(); // CAN idle task
void CAN_host_send_setup(bool change_status); // Send configuration to toolhead
uint32_t CAN_host_get_iostate(); // Read the CAN virtual IO state
HAL_StatusTypeDef CAN_host_start(); // Start the CAN device
HAL_StatusTypeDef CAN_host_stop(); // Stop the CAN device
HAL_StatusTypeDef CAN_host_send_gcode(); // Send Gcode to the toolhead
HAL_StatusTypeDef CAN_host_send_gcode_2params(uint32_t Gcode_type, uint32_t Gcode_no, uint32_t parameter1, float value1, uint32_t parameter2, float value2);

HAL_StatusTypeDef CAN_toolhead_start(); // Start the CAN device
void CAN_toolhead_send_update(bool tempUpdate); // Send an IO and temp update to the host
void CAN_toolhead_send_string(const char * message); // Send CAN string to host
void CAN_toolhead_idle();
29 changes: 29 additions & 0 deletions Marlin/src/MarlinCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@
#include "HAL/shared/esp_wifi.h"
#include "HAL/shared/cpu_exception/exception_hook.h"

#if ANY(CAN_HOST, CAN_TOOLHEAD)
#include "HAL/shared/CAN.h"
#endif

#if ENABLED(HAS_ADXL345_ACCELEROMETER)
#include "feature/accelerometer/acc_adxl345.h"
#endif

#if ENABLED(WIFISUPPORT)
#include "HAL/shared/esp_wifi.h"
#endif
Expand Down Expand Up @@ -882,6 +890,10 @@ void idle(const bool no_stepper_sleep/*=false*/) {
// Manage Fixed-time Motion Control
TERN_(FT_MOTION, ftMotion.loop());

TERN_(CAN_HOST, CAN_host_idle());

TERN_(CAN_TOOLHEAD, CAN_toolhead_idle());

IDLE_DONE:
TERN_(MARLIN_DEV_MODE, idle_depth--);

Expand Down Expand Up @@ -1236,6 +1248,23 @@ void setup() {

SETUP_RUN(hal.init());

#if ENABLED(CAN_HOST)
SERIAL_ECHOLN(
F(">>> CAN Start: "),
CAN_host_start() == HAL_OK ? F("OK") : F("FAILED!")
);
#endif

#if ENABLED(CAN_TOOLHEAD)
SERIAL_ECHOLN( F(">>> CAN Start: "),
CAN_toolhead_start() == HAL_OK ? F("OK") : F("FAILED!")
);
#endif

#if ENABLED(HAS_ADXL345_ACCELEROMETER)
adxl345.begin();
#endif

// Init and disable SPI thermocouples; this is still needed
#if TEMP_SENSOR_IS_MAX_TC(0) || (TEMP_SENSOR_IS_MAX_TC(REDUNDANT) && REDUNDANT_TEMP_MATCH(SOURCE, E0))
OUT_WRITE(TEMP_0_CS_PIN, HIGH); // Disable
Expand Down
14 changes: 12 additions & 2 deletions Marlin/src/feature/runout.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
#include "pause.h" // for did_pause_print
#include "../MarlinCore.h" // for printingIsActive()

#if ENABLED(CAN_HOST)
#include "../HAL/shared/CAN.h"
#endif

#include "../inc/MarlinConfig.h"

#if ENABLED(EXTENSIBLE_UI)
Expand All @@ -51,7 +55,13 @@
#define HAS_FILAMENT_SWITCH 1
#endif

#define FILAMENT_IS_OUT() (READ(FIL_RUNOUT_PIN) == FIL_RUNOUT_STATE)
#if ENABLED(CAN_HOST)
#define FILAMENT_IS_OUT() (bool(CAN_host_get_iostate() & CAN_ID_FILAMENT_MASK) == FIL_RUNOUT_STATE)
#define RUNOUT_STATE(N) bool(CAN_host_get_iostate() & CAN_ID_FILAMENT_MASK) // CAN Virtual Filament Runout pin
#else
#define FILAMENT_IS_OUT() (READ(FIL_RUNOUT_PIN) == FIL_RUNOUT_STATE)
#define RUNOUT_STATE(N) READ(FIL_RUNOUT##N##_PIN) // DIO Filament Runout pin
#endif

typedef Flags<
#if NUM_MOTION_SENSORS > NUM_RUNOUT_SENSORS
Expand Down Expand Up @@ -207,7 +217,7 @@ class FilamentSensorBase {

// Return a bitmask of runout pin states
static uint8_t poll_runout_pins() {
#define _OR_RUNOUT(N) | (READ(FIL_RUNOUT##N##_PIN) ? _BV((N) - 1) : 0)
#define _OR_RUNOUT(N) | (RUNOUT_STATE(N) ? _BV((N) - 1) : 0)
return (0 REPEAT_1(NUM_RUNOUT_SENSORS, _OR_RUNOUT));
#undef _OR_RUNOUT
}
Expand Down
14 changes: 14 additions & 0 deletions Marlin/src/gcode/gcode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ GcodeSuite gcode;
#include "../feature/fancheck.h"
#endif

#if ENABLED(CAN_HOST)
#include "../HAL/shared/CAN.h"
#include "../libs/buzzer.h"
#endif

#include "../MarlinCore.h" // for idle, kill

// Inactivity shutdown
Expand Down Expand Up @@ -323,6 +328,15 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {

KEEPALIVE_STATE(IN_HANDLER);

#if ENABLED(CAN_HOST)
if (CAN_host_send_gcode() != HAL_OK) { // Send command to toolhead
SERIAL_ECHOLN(F("Error: CAN failed to send \""), parser.command_ptr, '"');
#if ENABLED(CAN_DEBUG)
BUZZ(1, SOUND_ERROR);
#endif
}
#endif

/**
* Block all Gcodes except M511 Unlock Printer, if printer is locked
* Will still block Gcodes if M511 is disabled, in which case the printer should be unlocked via LCD Menu
Expand Down
8 changes: 8 additions & 0 deletions Marlin/src/gcode/host/M115.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
#include "../../feature/caselight.h"
#endif

#if ANY(CAN_HOST, CAN_TOOLHEAD)
#include "../../HAL/shared/CAN.h"
#endif

#if !defined(MACHINE_UUID) && ENABLED(HAS_STM32_UID)
#include "../../libs/hex_print.h"
#endif
Expand Down Expand Up @@ -76,6 +80,10 @@ void GcodeSuite::M115() {
TERN_(IS_CARTESIAN, "Cartesian") \
TERN_(BELTPRINTER, " BELTPRINTER")

#if ENABLED(CAN_TOOLHEAD)
MString<60> buffer("FIRMWARE ", __DATE__, " ", __TIME__, " Thermistor=", TEMP_SENSOR_0);
CAN_toolhead_send_string(buffer);
#endif
SERIAL_ECHOPGM("FIRMWARE_NAME:Marlin"
" " DETAILED_BUILD_VERSION " (" __DATE__ " " __TIME__ ")"
" SOURCE_CODE_URL:" SOURCE_CODE_URL
Expand Down
65 changes: 55 additions & 10 deletions Marlin/src/gcode/temp/M306.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
#include "../gcode.h"
#include "../../lcd/marlinui.h"
#include "../../module/temperature.h"
#include "../../libs/numtostr.h"

#if ENABLED(CAN_TOOLHEAD)
#include "../../HAL/shared/CAN.h"
#endif

/**
* M306: MPC settings and autotune
Expand Down Expand Up @@ -57,16 +62,34 @@ void GcodeSuite::M306() {

#if ENABLED(MPC_AUTOTUNE)
if (parser.seen_test('T')) {
Temperature::MPCTuningType tuning_type;
const uint8_t type = parser.byteval('S', 0);
switch (type) {
case 1: tuning_type = Temperature::MPCTuningType::FORCE_DIFFERENTIAL; break;
case 2: tuning_type = Temperature::MPCTuningType::FORCE_ASYMPTOTIC; break;
default: tuning_type = Temperature::MPCTuningType::AUTO; break;
}
LCD_MESSAGE(MSG_MPC_AUTOTUNE);
thermalManager.MPC_autotune(e, tuning_type);
ui.reset_status();

#if ENABLED(CAN_HOST) // Execute MPC autotune on toolhead

SERIAL_ECHOLNPGM(
">>> Forwarding M306 to toolhead\n"
">>> Store MPC setup in the host Configuration.h or use M500\n"
">>> MPC heater power is: ", p_float_t(MPC_HEATER_POWER, 1), " Watts\n"
">>> Please wait for the auto tune results..."
);

#else

Temperature::MPCTuningType tuning_type;
const uint8_t type = parser.byteval('S', 0);
switch (type) {
case 1: tuning_type = Temperature::MPCTuningType::FORCE_DIFFERENTIAL; break;
case 2: tuning_type = Temperature::MPCTuningType::FORCE_ASYMPTOTIC; break;
default: tuning_type = Temperature::MPCTuningType::AUTO; break;
}
LCD_MESSAGE(MSG_MPC_AUTOTUNE);
thermalManager.MPC_autotune(e, tuning_type);
ui.reset_status();

#if ENABLED(CAN_TOOLHEAD)
M306_report(true); // Report MPC autotune results to CAN host
#endif
#endif

return;
}
#endif
Expand All @@ -91,6 +114,11 @@ void GcodeSuite::M306_report(const bool forReplay/*=true*/) {
TERN_(MARLIN_SMALL_BUILD, return);

report_heading(forReplay, F("Model predictive control"));

#if ENABLED(CAN_HOST) // MPC Autotune info
if (forReplay) SERIAL_ECHOLNPGM(">>> Host M306 MPC settings:");
#endif

HOTEND_LOOP() {
report_echo_start(forReplay);
MPC_t &mpc = thermalManager.temp_hotend[e].mpc;
Expand All @@ -105,6 +133,23 @@ void GcodeSuite::M306_report(const bool forReplay/*=true*/) {
#endif
SERIAL_ECHOLNPGM(" H", p_float_t(mpc.filament_heat_capacity_permm, 4));
}

#if ENABLED(CAN_TOOLHEAD) // Report M306 Autotune results to host
if (forReplay) {
MPC_t &mpc = thermalManager.temp_hotend[0].mpc;
MString<100> buffer(F("M306 E0 P"), p_float_t(mpc.heater_power, 2),
" C", p_float_t(mpc.block_heat_capacity, 2),
" R", p_float_t(mpc.sensor_responsiveness, 4),
" A", p_float_t(mpc.ambient_xfer_coeff_fan0, 4),
#if ENABLED(MPC_INCLUDE_FAN)
" F", p_float_t(mpc.fanCoefficient(), 4),
#endif
" H", p_float_t(mpc.filament_heat_capacity_permm, 4));

CAN_toolhead_send_string(buffer);
}
#endif // CAN_TOOLHEAD

}

#endif // MPCTEMP
2 changes: 1 addition & 1 deletion Marlin/src/inc/SanityCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ static_assert(COUNT(arm) == LOGICAL_AXES, "AXIS_RELATIVE_MODES must contain " _L
#if ENABLED(SERIAL_DMA)
#ifdef ARDUINO_ARCH_HC32
// checks for HC32 are located in HAL/HC32/inc/SanityCheck.h
#elif DISABLED(HAL_STM32) || NONE(STM32F0xx, STM32F1xx, STM32F2xx, STM32F4xx, STM32F7xx)
#elif DISABLED(HAL_STM32) || NONE(STM32F0xx, STM32F1xx, STM32F2xx, STM32F4xx, STM32F7xx, STM32H7xx)
#error "SERIAL_DMA is only available for some STM32 MCUs and requires HAL/STM32."
#elif !defined(HAL_UART_MODULE_ENABLED) || defined(HAL_UART_MODULE_ONLY)
#error "SERIAL_DMA requires STM32 platform HAL UART (without HAL_UART_MODULE_ONLY)."
Expand Down
Loading