From ef969ffe0a2464a83935de5e87fe7203e04baad3 Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Wed, 27 Nov 2024 00:54:56 +0800 Subject: [PATCH 01/21] Added CAN host support Added experimental CAN host support to communicate with BTT EBB42 V1.2 CAN head board running Marlin --- Marlin/Configuration.h | 4 +- Marlin/src/HAL/STM32/CAN.cpp | 752 ++++++++++++++++++++++++++++++ Marlin/src/HAL/STM32/CAN.h | 28 ++ Marlin/src/HAL/STM32/Servo.cpp | 15 + Marlin/src/MarlinCore.cpp | 13 +- Marlin/src/feature/pause.cpp | 5 + Marlin/src/feature/runout.h | 10 + Marlin/src/gcode/gcode.cpp | 15 + Marlin/src/gcode/temp/M306.cpp | 15 + Marlin/src/module/endstops.cpp | 12 + Marlin/src/module/probe.h | 23 +- Marlin/src/module/temperature.cpp | 13 + ini/stm32f4.ini | 5 + platformio.ini | 2 +- 14 files changed, 906 insertions(+), 6 deletions(-) create mode 100644 Marlin/src/HAL/STM32/CAN.cpp create mode 100644 Marlin/src/HAL/STM32/CAN.h diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index c45c75a39d96..d4ba2265dd75 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -37,6 +37,8 @@ */ #define CONFIGURATION_H_VERSION 02010300 +// ENABLE CAN SUPPORT HERE +// #define CAN_MASTER //=========================================================================== //============================= Getting Started ============================= //=========================================================================== @@ -68,7 +70,7 @@ // Choose the name from boards.h that matches your setup #ifndef MOTHERBOARD - #define MOTHERBOARD BOARD_RAMPS_14_EFB + #define MOTHERBOARD #define MOTHERBOARD BOARD_MKS_MONSTER8_V2 #endif // @section serial diff --git a/Marlin/src/HAL/STM32/CAN.cpp b/Marlin/src/HAL/STM32/CAN.cpp new file mode 100644 index 000000000000..52cfeca1d2d7 --- /dev/null +++ b/Marlin/src/HAL/STM32/CAN.cpp @@ -0,0 +1,752 @@ +// IRON NOTES: +// NOTE 1: For MKS Monster 8 V1/V2 on Arduino use: Board "Generic STM32F4 series", Board part number "Generic F407VETx" +// NOTE 2: Make sure to define "HAL_CAN_MODULE_ENABLED". In VSCode add "build_flags = -D HAL_CAN_MODULE_ENABLED" in platformio.ini. +// For Arduino IDE include "hal_conf_extra.h" holding "#define HAL_CAN_MODULE_ENABLED" +// NOTE 3: To accept all CAN messages, enable 1 filter (FilterBank = 0) in "FilterMode = CAN_FILTERMODE_IDMASK", mask and ID = 0 (0=don't care) +// NOTE 4: Serial communication in ISR causes issues! Hangs etc. so avoid this! +// NOTE 5: A FIFO storage cell is called a Mailbox in STM32F4xx, FIFO0 and FiFO1 can hold 3 CAN message each. +// NOTE 6: The filter ID/mask numbers (LOW/HIGH) do not directly relate to the message ID numbers (See Figure 342 in RM0090) + +#include "../../inc/MarlinConfig.h" + +#ifdef CAN_MASTER + #include "../platforms.h" + #include "../../gcode/gcode.h" + #include "../../module/temperature.h" + #include "../../module/motion.h" // For current_position variable + #include "../../module/planner.h" // For steps/mm parameters variables + #include "../../feature/tmc_util.h" + #include "../../module/stepper.h" + #include "../../module/endstops.h" + #include "../../feature/controllerfan.h" // For controllerFan settings + #include "../../libs/numtostr.h" // For float to string conversion + + // #define CAN_DEBUG // Define to show gcodes send to HEAD + + #define CAN_EXTENDED_ID CAN_ID_EXT + #define CAN_STANDARD_ID CAN_ID_STD + + #define STDID_FIFO_BIT 0b10000000000 + #define EXTID_FIFO_BIT 0x10000000 + + #define CAN_IO_MASK 0b11111 // Masks for the 5 virtual IO bits (see below) + #define GCODE_NUMBER_MASK 0b1111111111111 + #define PARAMETER_MASK 0b11111 + #define PARAMETER_COUNT_MASK 0b111 + #define GCODE_TYPE_MASK 0b11 + #define CAN_PROBE_MASK 1 // Virtual IO bit for probe + #define CAN_FILAMENT_MASK 2 // Virtual IO bit for filament + #define CAN_X_ENDSTOP_MASK 4 // Virtual IO bit for X-endstop + #define CAN_Y_ENDSTOP_MASK 8 // Virtual IO bit for Y-endstop + #define CAN_Z_ENDSTOP_MASK 16 // Virtual IO bit for Z-endstop + #define CAN_STRING_MESSAGE_MASK 32 // Signals the head sent a string message + #define CAN_REQUEST_SETUP_MASK 64 // Signals the head requests setup information + #define CAN_TMC_OT_MASK 128 // Signals the head signals a TMC Over Temp error + #define CAN_E0_TARGET_MASK 256 // Signals E0 or E1 + #define CAN_ERROR_MASK 512 // Signals the head encountered an error + + #define PARAMETER1_OFFSET 0 + #define PARAMETER2_OFFSET 5 + #define GCODE_NUMBER_OFFSET 10 + #define GCODE_TYPE_OFFSET 23 + #define PARAMETER_COUNT_OFFSET 25 + + #define GCODE_TYPE_D 0 + #define GCODE_TYPE_G 1 + #define GCODE_TYPE_M 2 + #define GCODE_TYPE_T 3 + +extern "C" void CAN1_RX0_IRQHandler(void); // Override weak CAN FIFO0 interrupt handler +extern "C" void CAN1_RX1_IRQHandler(void); // Override weak CAN FIFO1 interrupt handler +extern "C" void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan); // Override weak CAN interrupt callback for FIFO0 +extern "C" void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan); // Override weak CAN interrupt callback for FIFO1 +extern "C" void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan); // Override weak CAN interrupt callback + +CAN_HandleTypeDef hcan1 = { 0 }; // The CAN1 handle +CAN_TxHeaderTypeDef TxHeader = { 0 }; // Header to send a CAN message +volatile uint32_t CAN_io_state = 0; // Virtual IO state variable +volatile bool CAN_head_error = 0; // Register if an error was reported by the head +volatile bool CAN_head_setup_request = false; // Signals the head requesting setup information +volatile uint32_t gcode_counter = 0; // Count amount of gcodes received +volatile uint32_t HAL_CAN_error_code = 0; // Record a host CAN error message + +volatile bool FirstE0Error = true; // First CAN bus error, show warning only once +volatile bool string_message_complete = false; // Signals a complete string message was received +volatile uint32_t string_message_index = 0; // Index into the CAN string that is being received +uint32_t Last_CAN_Temp_Report = 0; // Track when the last head temperature report was received +uint32_t Last_CAN_Error_Message = 0; // Track when the last CAN error messaget was shown +char string_message[128] = "\0"; // CAN string message buffer for incoming message, max 128 characters + +void CAN1_RX0_IRQHandler(void) // CAN FIFO0 Interrupt handler overrides standard weak CAN1_RX0_IRQHandler +{ + HAL_CAN_IRQHandler(&hcan1); // Forwarded for callbacks --> HAL_CAN_RxFifo0MsgPendingCallback/HAL_CAN_ErrorCallback +// OR +// HAL_CAN_RxFifo0MsgPendingCallback(&hcan1); // Call the required callback directly, faster but no error reporting +} + +void CAN1_RX1_IRQHandler(void) // CAN FIFO1 Interrupt handler overrides standard weak CAN1_RX0_IRQHandler +{ + HAL_CAN_IRQHandler(&hcan1); // Forwarded for callbacks --> HAL_CAN_RxFifo1MsgPendingCallback/HAL_CAN_ErrorCallback +// OR +// HAL_CAN_RxFifo1MsgPendingCallback(&hcan1); // Call the required callback directly, faster but no error reporting +} + +// Send specified Gcode with max 2 parameters and 2 values via CAN bus +HAL_StatusTypeDef CAN_Send_Gcode_2params(uint32_t Gcode_type, uint32_t Gcode_no, uint32_t parameter1, float value1, uint32_t parameter2, float value2) +{ + switch (Gcode_type) + { + case 'D': Gcode_type = GCODE_TYPE_D; + break; + + case 'G': Gcode_type = GCODE_TYPE_G; + break; + + case 'M': Gcode_type = GCODE_TYPE_M; + +#ifdef CAN_DEBUG + SERIAL_ECHOPGM(">>> CAN TO HEAD: M", Gcode_no); + if (parameter1) { + SERIAL_CHAR(' ', parameter1); + if (value1 == int(value1)) + SERIAL_ECHO(i16tostr3left(value1)); // Integer value + else + SERIAL_ECHO(p_float_t(value1, 4)); // Float with 4 digits + } + + if (parameter2) { + SERIAL_CHAR(' ', parameter2); + if (value2 == int(value2)) + SERIAL_ECHO(i16tostr3left(value2)); // Integer value + else + SERIAL_ECHO(p_float_t(value2, 4)); // Float with 4 digits + } + SERIAL_EOL(); +#endif + + break; + + case 'T': Gcode_type = GCODE_TYPE_T; + break; + + default: return HAL_ERROR; // UNKNOWN GCODE TYPE + } + + HAL_StatusTypeDef status = HAL_OK; + uint32_t TxMailbox; // Stores which Mailbox (0-2) is used to store the Sent TX message + + if (parameter1 > 31) + parameter1 -= 64; // Format 'A' = 1, 'B' = 2, etc. + + if (parameter2 > 31) + parameter2 -= 64; // Format 'A' = 1, 'B' = 2, etc. + + TxHeader.IDE = CAN_EXTENDED_ID; + TxHeader.DLC = 4 * (!!parameter1 + !!parameter2); // Amount of bytes to send (4 or 8) + TxHeader.StdId = (TxHeader.StdId ^ STDID_FIFO_BIT); // Toggle FIFO bit 10, keep FIFO toggling in sync + TxHeader.ExtId = ((TxHeader.ExtId ^ EXTID_FIFO_BIT) & EXTID_FIFO_BIT) + // Toggle FIFO bit 28 + ((TxHeader.DLC >> 2) << PARAMETER_COUNT_OFFSET) + // Data bytes (4 or 8) + (Gcode_type << GCODE_TYPE_OFFSET) + // G/M/T/D-code + ((Gcode_no & GCODE_NUMBER_MASK) << GCODE_NUMBER_OFFSET) + // Gcode number + ((parameter2 & PARAMETER_MASK) << PARAMETER2_OFFSET) + // First parameter + ((parameter1 & PARAMETER_MASK) << PARAMETER1_OFFSET); // Second parameter + uint8_t CAN_tx_buffer[8]; // 8 bytes CAN data TX buffer + float * fp = (float *)CAN_tx_buffer; // Point to TX buffer + *fp++ = value1; + *fp-- = value2; + + uint32_t ms = millis(); // Don't send too fast! + while ((HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) == 0) && (millis() - ms < 25)) { } // BLOCKING! Wait max 25ms! + // SERIAL_ECHOLNPGM(">>> Waited1: ", millis() - ms, " FreeTX: ", HAL_CAN_GetTxMailboxesFreeLevel(&hcan1)); // IRON, DEBUGGING + status = HAL_CAN_AddTxMessage(&hcan1, &TxHeader, CAN_tx_buffer, &TxMailbox); // Send message + + if (status == HAL_OK) // Count sent gcode messages + gcode_counter++; + + return status; +} + +HAL_StatusTypeDef CAN_Send_Setup() // Send setup to HEAD +{ + CAN_head_setup_request = false; + SERIAL_ECHOLNPGM(">>> CAN: SENDING CONFIG TO HEAD ====="); +// NOTE: Sending many command too fast will cause a Marlin command buffer overrun at the head, add delays if needed +// CAN_Send_Gcode_2params('M', 104, 'S', 0, 0, 0); // M104 S0, switch off hotend heating, NOT NEEDED ANYMORE +// CAN_Send_Gcode_2params('M', 107, 0, 0, 0, 0); // M107, switch off cooling fan, NOT NEEDED ANYMORE +// CAN_Send_Gcode_2params('M', 18, 0, 0, 0, 0); // M18, switch off steppers, NOT NEEDED ANYMORE + + // M306 MPC settings (managed by host) + MPC_t &mpc = thermalManager.temp_hotend[0].mpc; + + CAN_Send_Gcode_2params('M', 306, 'A', mpc.ambient_xfer_coeff_fan0, 'C', mpc.block_heat_capacity); // M306 R A + CAN_Send_Gcode_2params('M', 306, 'F', mpc.fanCoefficient(), 'H', mpc.filament_heat_capacity_permm); // M306 F H + CAN_Send_Gcode_2params('M', 306, 'P', mpc.heater_power, 'R', mpc.sensor_responsiveness); // M306 P C + +// CAN_Send_Gcode_2params('M', 150, 0, 0, 0, 0); // M150, SWITCH NEOPIXEL OFF + +/* + extern Planner planner; // M92 Steps per mm + CAN_Send_Gcode_2params('M', 92, 'X', planner.settings.axis_steps_per_mm[X_AXIS], 'Y', planner.settings.axis_steps_per_mm[Y_AXIS]); + CAN_Send_Gcode_2params('M', 92, 'Z', planner.settings.axis_steps_per_mm[Z_AXIS], 'E', planner.settings.axis_steps_per_mm[E_AXIS]); + + // M200 Set filament diameter + CAN_Send_Gcode_2params('M', 200, 'S', parser.volumetric_enabled, 'D', LINEAR_UNIT(planner.filament_size[0])); + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + CAN_Send_Gcode_2params('M', 200, 'L', LINEAR_UNIT(planner.volumetric_extruder_limit[0]) + #endif + + // M201 Max acceleration + CAN_Send_Gcode_2params('M', 201, 'X', planner.settings.max_acceleration_mm_per_s2[X_AXIS], 'Y', planner.settings.max_acceleration_mm_per_s2[Y_AXIS]); + CAN_Send_Gcode_2params('M', 201, 'Z', planner.settings.max_acceleration_mm_per_s2[Z_AXIS], 'E', planner.settings.max_acceleration_mm_per_s2[E_AXIS]); + + // M203 Max feedrate + CAN_Send_Gcode_2params('M', 203, 'X', planner.settings.max_feedrate_mm_s[X_AXIS], 'Y', planner.settings.max_feedrate_mm_s[Y_AXIS]); + CAN_Send_Gcode_2params('M', 203, 'Z', planner.settings.max_feedrate_mm_s[Z_AXIS], 'E', planner.settings.max_feedrate_mm_s[E_AXIS]); + + // M204 Accelerations in units/sec^2, ENABLED BECAUSE IT INFORMS THE HEAD THE CONFIGURATION WAS SENT + CAN_Send_Gcode_2params('M', 204, 'P', planner.settings.acceleration, 'R', planner.settings.retract_acceleration); + CAN_Send_Gcode_2params('M', 204, 'T', planner.settings.travel_acceleration, 0, 0); + + // M205 + #ifdef CLASSIC_JERK + CAN_Send_Gcode_2params('M', 205,'S')) planner.settings.min_feedrate_mm_s, 'T')) planner.settings.min_travel_feedrate_mm_s); + CAN_Send_Gcode_2params('M', 205, M205_MIN_SEG_TIME_PARAM, planner.settings.min_segment_time_us, 'J', planner.junction_deviation_mm); + CAN_Send_Gcode_2params('M', 205, 'X', LINEAR_UNIT(planner.max_jerk.x), 'Y', LINEAR_UNIT(planner.max_jerk.y)); + CAN_Send_Gcode_2params('M', 205, 'Z', LINEAR_UNIT(planner.max_jerk.z), 'E', LINEAR_UNIT(planner.max_jerk.e)); + CAN_Send_Gcode_2params('M', 205, 'J', LINEAR_UNIT(planner.junction_deviation_mm), 0, 0); + #endif + + // M206 Home offset + #ifndef NO_HOME_OFFSETS + _CAN_Send_Gcode_2params('M', 206, 'X', LINEAR_UNIT(home_offset.x), 'Y', LINEAR_UNIT(home_offset.y)); + CAN_Send_Gcode_2params('M', 206, 'Z', LINEAR_UNIT(home_offset.z), 0, 0); + #endif + + // M207 Set Firmware Retraction + // M208 - Firmware Recover + // M209 - Set Auto Retract + + // M220 Speed/feedrate + CAN_Send_Gcode_2params('M', 220, 'S', feedrate_percentage, 0, 0); + + // M221 Flow percentage + CAN_Send_Gcode_2params('M', 221, 'T', 0, 'S', planner.flow_percentage[0]); + // CAN_Send_Gcode_2params('M', 221, 'T', 1, 'S', planner.flow_percentage[1]); // For 2nd extruder + + // M302 Cold extrude settings + #if ENABLED(PREVENT_COLD_EXTRUSION) + CAN_Send_Gcode_2params('M', 302, 'P', '0' + thermalManager.allow_cold_extrude, 'S', thermalManager.extrude_min_temp); // P0 enable cold extrusion checking, P1 = disabled, S=Minimum temperature + #endif + + // M569 TMC Driver StealthChop/SpreadCycle + CAN_Send_Gcode_2params('M', 569, 'S', stepperE0.get_stored_stealthChop(), 'E', 0); // M569 S[0/1] E + + // M592 Nonlinear Extrusion Control + + // M916 TMC Motor current + CAN_Send_Gcode_2params('M', 906, 'E', stepperE0.getMilliamps(), 0, 0); + + // M919 TMC Chopper timing for E only + CAN_Send_Gcode_2params('M', 919, 'O', off, 'P' , Hysteresis End); + CAN_Send_Gcode_2params('M', 919, 'S', Hysteresis Start, 0, 0); +*/ +/* +CAN_Send_Gcode_2params('M', 710, 'E', 1, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P +CAN_Send_Gcode_2params('M', 710, 'E', 2, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P +CAN_Send_Gcode_2params('M', 710, 'E', 3, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P +CAN_Send_Gcode_2params('M', 710, 'E', 4, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P +CAN_Send_Gcode_2params('M', 710, 'E', 5, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P +CAN_Send_Gcode_2params('M', 710, 'E', 6, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P +CAN_Send_Gcode_2params('M', 710, 'E', 7, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P +CAN_Send_Gcode_2params('M', 710, 'E', 8, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P +CAN_Send_Gcode_2params('M', 710, 'E', 9, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P +CAN_Send_Gcode_2params('M', 710, 'E', 10, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P +CAN_Send_Gcode_2params('M', 710, 'E', 11, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P +*/ + // IRON, M710 SIGNALS TO THE HEAD THAT THE CAN CONFIGURATION IS COMPLETE, USE IT AS THE LAST GCODE TO SEND + return CAN_Send_Gcode_2params('M', 710, 'E', int(controllerFan.settings.extruder_auto_fan_speed), 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P +} + +void CAN_Idle() // Tasks that cannot be done in the ISR +{ + if (CAN_head_setup_request) // The head requested the setup data + CAN_Send_Setup(); + + if (string_message_complete) // Received string message is complete + { + BUZZ(1, SOUND_OK); + SERIAL_ECHOPGM(">>> CAN MSG FROM HEAD: "); + for (uint32_t i = 0; i < string_message_index; i++) + SERIAL_CHAR(string_message[i]); // Show received string message, ends on '\n' + + string_message_complete = false; + string_message_index = 0; + } + + if ((hcan1.ErrorCode || CAN_head_error || HAL_CAN_error_code) && (millis() - Last_CAN_Error_Message > 6000)) + { + BUZZ(1, SOUND_ERROR); // Warn with sound + if (CAN_head_error) + SERIAL_ECHOLNPGM(">>> CAN ERROR REPORTED BY HEAD"); + CAN_head_error = false; // Reset, but will be repeated by the head + + if (HAL_CAN_error_code) + SERIAL_ECHOLNPGM(">>> HAL CAN ERROR REPORTED: ", HAL_CAN_error_code); + + if (hcan1.ErrorCode) + SERIAL_ECHOLNPGM(">>> hcan1.ErrorCode=", hcan1.ErrorCode); + + Last_CAN_Error_Message = millis(); + } + + if ((millis() - Last_CAN_Temp_Report) > 10000) // IRON, ERROR, NO TEMP UPDATE RECEIVED IN 10 SECONDS, KILL PRINTER + { + Last_CAN_Temp_Report = millis(); + if (FirstE0Error) // Send error notification + { + BUZZ(1, SOUND_ERROR); // Warn with sound + SERIAL_ECHOLNPGM("Error: NO CAN E0 TEMP UPDATES!"); + } + else // Send only error message + SERIAL_ECHOLNPGM(">>> CAN ERROR, NO E0 TEMP UPDATES!"); + + FirstE0Error = false; // Warn only once + + #ifndef IRON_DEBUG // Only kill if not debugging + kill(F("CAN ERROR, NO E0 TEMPERATURE UPDATES")); + #endif + } +} + +HAL_StatusTypeDef CAN_Send_Gcode() // Forward a Marlin Gcode via CAN (uses parser.command_letter, Gcode_no, parser.value_float()) +{ // Send a Gcode to the head with parameters and values + // Gcode starts with extended frame which can send the Gcode with max 2 parameters and values. + // Extended frames are send to complete all parameters and values (max 2 per extended message). + // 1. Analyze Gcode command + // 2. Ignore gcodes that do not need to be forwarded + // 3. Send parameters and values + // char s[] = "G0 X123.45678 Y124.45678 Z125.45678 E126.45678 F127.45678\n"; + + HAL_StatusTypeDef status = HAL_OK; + uint32_t TxMailbox; // Stores which Mailbox (0-2) is used to store the Sent TX message + + if (parser.command_letter != 'M') // Only forward Mxxx Gcode to head + return HAL_OK; + + uint32_t Gcode_type = GCODE_TYPE_M; // M-code, fixed for now + uint32_t Gcode_no = parser.codenum; + + if (Gcode_no == 109) // Convert M109(Hotend wait) to M104 (no wait) to keep the head responsive + Gcode_no = 104; + + if ((Gcode_no == 501) || (Gcode_no == 502)) // M501=Restore settings, M502=Factory defaults + CAN_head_setup_request = true; // Also update settings for the head + + if ((Gcode_no != 104) && // Set hotend target temp + (Gcode_no != 106) && // Set cooling fan speed + (Gcode_no != 107) && // Cooling fan off + (Gcode_no != 150) && // Set NeoPixel values +// (Gcode_no != 108) && // Break and Continue + (Gcode_no != 280) && // Servo position + (Gcode_no != 306) && // MPC settings/tuning + (Gcode_no != 710) && // Control fan PWM + (Gcode_no != 997)) // Reboot + return HAL_OK; // Nothing to do + + uint32_t index; + uint32_t parameter_counter = 0; + char letters[] = "XYZEFPSABCHIJKLOQRTUVW"; // All possible parameters (22), defines scan order, no "D G M N", includes 'T' for autotune (M306 T) + static uint32_t parameters[8] = { 0 }; // Store found parameters, send max 7 parameters (send in pairs, so reserve 8), CodeA=1 (ASCII65), CodeE=5, CodeF=6, CodeX=88-64=24, CodeY=89-64=25, CodeZ=90-64=26 + static float values[8] = { 0 }; // Store found values, send max 7 parameters (send in pairs, so reserve 8) + + uint8_t CAN_tx_buffer[8]; // 8 bytes CAN data TX buffer + + /* + switch (parser.command_letter) // Filter/adjust Gcodes + { + case 'G': Gcode_type = GCODE_TYPE_G; + switch (Gcode_no) + { + case 12: break; // No Nozzle cleaning support needed on head + case 29: case 34: return HAL_OK; // No bedleveling/Z-syncing on head + break; + } + break; + + case 'M': Gcode_type = GCODE_TYPE_M; + switch (Gcode_no) + { // Save Prog mem: M112, M48, M85, M105, M114, M155, M500, M501, M502, M503, M226, M422 + case 109: Gcode_no = 104; break; // Replace M109 with M104 + case 112: Gcode_no = 104; break; // Don't shutdown board, should stop heating with "M104" + + case 20: case 21: case 22: case 23: case 24: case 25: case 26: case 524: case 540: case 928: + case 27: case 28: case 29: case 30: case 32: case 33: case 34: // No SD file commands + case 43: // No pin debug + case 48: // No repeatability test + case 85: // No inactivity shutdown + case 100: // No show free memory support + case 108: // Break and Continue + case 105: // No temperature reporting + case 114: // Don't report position + case 117: case 118: case 119: // Don't send strings + case 140: case 190: // Ignore bed temp commands + case 150: // Set NeoPixel values + case 154: // No auto position reporting + case 155: // No tempeature reporting + case 226: // Wait for pin state + case 240: // No camera support + case 250: // No LCD contrast support + case 260: case 261: // No I2C on head + case 280: // Don't send servo angle, done via Servo.cpp already + case 290: // No baby stepping + case 300: // No tones + // case 303: // No PID autotune (done on HEAD) + case 304: // No bed PID settings + // case 306: // MPC autotune (done on HEAD) + case 350: case 351: // No live microstepping adjustment + case 380: case 381: // No solenoid support + case 401: case 402: // No probe deploy/stow, done via M280 servo angles + case 412: // Filament runout sensor done by MASTER + case 420: case 421: // No bed leveling state + case 423: // No X Twist Compensation + case 425: // No backlash compensation + case 500: case 501: case 502: case 503: case 504: case 910: // No EEPROM on head, remove M50x commands to save Prog mem + case 510: case 511: case 512: // No locking of the machine + case 605: // No IDEX commands + case 810: case 811: case 812: case 813: case 814: case 815: case 816: case 817: case 818: case 819: + case 851: // + case 871: // No Probe temp config + case 876: // No handle prompt response + case 913: // No Set Hybrid Threshold Speed + case 914: // No TMC Bump Sensitivity + case 997: // No remote reset + case 998: // No ESP3D reset + return HAL_OK; // NO CAM MESSAGE + } + break; + + case 'T': Gcode_type = GCODE_TYPE_T; + switch (Gcode_no) + { + case 0: case 1: + break; + } + break; + + case 'D': Gcode_type = GCODE_TYPE_D; + switch (Gcode_no) + { + case 0: case 1: + break; + } + break; + default: return HAL_OK; // Invalid command, nothing to do + } +*/ + + #ifdef CAN_DEBUG + SERIAL_ECHOPGM(">>> CAN GCODE TO HEAD: "); // IRON, DEBUGGING + SERIAL_CHAR(parser.command_letter); + SERIAL_ECHO(Gcode_no); // IRON, DEBUGGING + #endif + + if (strlen(parser.command_ptr) > 4) // "M107\0", ONLY SCAN FOR PARAMETERS IF STRING IS LONG ENOUGH + for (index = 0; index < sizeof(letters); index++) // Scan parameters + { + if (parser.seen(letters[index])) + { + parameters[parameter_counter] = letters[index] - 64; // Store parameter letter, A=1, B=2... + + #ifdef CAN_DEBUG + SERIAL_CHAR(' ', letters[index]); // IRON, DEBUGGING + #endif + + if (parser.has_value()) // Check if there is a value + { + values[parameter_counter++] = parser.value_float(); + + #ifdef CAN_DEBUG + if (values[parameter_counter - 1] == int(values[parameter_counter - 1])) + SERIAL_ECHO(i16tostr3left(values[parameter_counter - 1])); // Integer value + else + SERIAL_ECHO(p_float_t(values[parameter_counter - 1], 4)); // Float with 4 digits + #endif + + } + else // No value for parameter + values[parameter_counter++] = NAN; // Not A Number, indicates no parameter value is present + } + + if (parameter_counter == 8) + { + parameter_counter--; // Max is 7 parameters + SERIAL_ECHOPGM("\nError: TOO MANY PARAMETERS (> 7): "); + SERIAL_ECHOLN_P(parser.command_ptr); + BUZZ(1, SOUND_ERROR); + break; + } + } + #ifdef CAN_DEBUG + SERIAL_EOL(); + #endif + + parameters[parameter_counter] = 0; // Set next parameter to 0 (0=no parameter), send in pairs + index = 0; + float * fp = (float *)CAN_tx_buffer; // Points to TX buffer + + if ((Gcode_no == 710) && (parameters[0] == 3)) // "M710 C" INDICATES REQUEST FOR GCODE COUNT + SERIAL_ECHOLNPGM(">>> GCODES SENT: ", gcode_counter); + + gcode_counter++; + + TxHeader.IDE = CAN_EXTENDED_ID; // Start with EXTENDED_ID then send STANDARD_ID if needed + //TxHeader.ExtId &= EXTID_FIFO_BIT; // Clear ID, keep FIFO bit + TxHeader.ExtId = (TxHeader.ExtId & EXTID_FIFO_BIT) + // KEEP FIFO BIT + ((parameter_counter & PARAMETER_COUNT_MASK) << PARAMETER_COUNT_OFFSET) + // Parameter count + ((Gcode_type & GCODE_TYPE_MASK) << GCODE_TYPE_OFFSET) + // GCODE TYPE (G/M/T/D) + ((Gcode_no & GCODE_NUMBER_MASK) << GCODE_NUMBER_OFFSET) + // GCODE NUMBER + ((parameters[1] & PARAMETER_MASK) << PARAMETER2_OFFSET) + // PARAMETER2 + ((parameters[0] & PARAMETER_MASK) << PARAMETER1_OFFSET); // PARAMETER1 + uint32_t ms = millis(); // Record message send start time + do + { + TxHeader.DLC = MIN(8, (parameter_counter - index) << 2); // Maximum 8 bytes, 4 bytes if there is only 1 parameter, 8 bytes if there are 2 + //TxHeader.StdId = (TxHeader.StdId ^ STDID_FIFO_BIT) & STDID_FIFO_BIT; // Toggle FIFO bit 10, clear other bits + TxHeader.StdId = ((TxHeader.StdId ^ STDID_FIFO_BIT) & STDID_FIFO_BIT) + // Toggle FIFO bit + ((parameters[index + 1] & PARAMETER_MASK) << PARAMETER2_OFFSET) + // Parameter 2 + ((parameters[index ] & PARAMETER_MASK) << PARAMETER1_OFFSET); // Parameter 1 + TxHeader.ExtId ^= EXTID_FIFO_BIT; // Toggle FIFO bit 28 + *fp++ = values[index++]; // Copy first parameter value to data, move pointer to next 4 bytes + *fp-- = values[index++]; // Copy 2nd parameter value to data, move pointer to beginning of data array for next round + + while ((HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) == 0) && (millis() - ms < 50)) { } // BLOCKING! Wait max 50ms + status = HAL_CAN_AddTxMessage(&hcan1, &TxHeader, CAN_tx_buffer, &TxMailbox); // Send message + + if (status != HAL_OK) + return status; + + TxHeader.IDE = CAN_STANDARD_ID; // All following messages have standard ID for parameter values, 11 bits identifier + } while (index < parameter_counter); + + return HAL_OK; +} + +void CAN_Send_Position() // Send the X, Y, Z and E position to the HEAD +{ + CAN_Send_Gcode_2params('G', 92, 'X', current_position.x, 'Y', current_position.y); // M92 X Y + CAN_Send_Gcode_2params('G', 92, 'Z', current_position.z, 'E', current_position.e); // M92 E Z +} + +void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle) // Called by HAL_CAN_Init +{ + GPIO_InitTypeDef GPIO_InitStruct = {0}; + RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; + + if (canHandle->Instance == CAN1) + { + RCC_PeriphCLKInitTypeDef periphClkInit = { }; + HAL_RCCEx_GetPeriphCLKConfig(&periphClkInit); + periphClkInit.PeriphClockSelection |= RCC_APB1ENR_CAN1EN; // DONE IN __HAL_RCC_CAN1_CLK_ENABLE? + + if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) + Error_Handler(); + + // CAN1 clock enable + if (__HAL_RCC_CAN1_IS_CLK_DISABLED()); // Enable CAN1 clock + __HAL_RCC_CAN1_CLK_ENABLE(); // Enable CAN1 clock + + if (__HAL_RCC_GPIOB_IS_CLK_DISABLED()) // Should be enabled by Marlin already + __HAL_RCC_GPIOB_CLK_ENABLE(); // Enable GPIO B clock + // CAN1 GPIO Configuration + // PB8 ------> CAN1_RX + // PB9 ------> CAN1_TX + + GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9; // Pin PB8 and Pin PB9 + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF9_CAN1; // Alternate function 9 + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // Port B + + // CAN1 interrupt Init + HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 1); + HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn); // Enable CAN FIFO1 interrupt handler + + HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 1, 1); + HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn); // Enable CAN FIFO1 interrupt handler + } +} + +HAL_StatusTypeDef CAN1_Stop(void) +{ + return HAL_CAN_Stop(&hcan1); +} + +HAL_StatusTypeDef CAN1_Start(void) +{ + HAL_StatusTypeDef status = HAL_OK; + + // Init TxHeader with constant values + TxHeader.ExtId = 0; + TxHeader.StdId = 0; + TxHeader.RTR = CAN_RTR_DATA; // Data transmission type: CAN_RTR_DATA / CAN_RTR_REMOTE + TxHeader.TransmitGlobalTime = DISABLE; // Put timestamp in Data[6-7], requires Time Triggered Communication Mode + + // CAN baud rate = clock frequency / clock divider / prescaler / (1 + TSG1 + TSG2) + // Baud rate = 42M / 3 / 1 / (1 + 11 + 2) = 1M baud + // Baud rate = 42M / 3 / 2 / (1 + 11 + 2) = 500k baud + // Baud rate = 42M / 3 / 4 / (1 + 11 + 2) = 250k baud + hcan1.Instance = CAN1; + hcan1.Init.Prescaler = 3; // 1-1024, 42MHz peripheral clock / 3 --> 14MHz -> 1M baud. 6 --> 500K baud. 12 --> 250K baud. + hcan1.Init.AutoBusOff = DISABLE; // DISABLE: Software controlled Bus-off. ENABLE: Automatic hardware controlled (no send/receive) + hcan1.Init.AutoWakeUp = ENABLE; // ENABLE: Automatic hardware controlled bus wakeup. DISABLE: Software controlled bus wakeup. + hcan1.Init.AutoRetransmission = ENABLE; // DISABLE / ENABLE, resend if transmission failed, but locks up if communication fails/cable not connected!!!!!!!!!!!!!!!!! + hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ; // CAN_SJW_1TQ + hcan1.Init.TimeSeg1 = CAN_BS1_11TQ; // CAN_BS1_11TQ + hcan1.Init.TimeSeg2 = CAN_BS2_2TQ; // CAN_BS2_2TQ + hcan1.Init.Mode = CAN_MODE_NORMAL; // CAN_MODE_NORMAL / CAN_MODE_SILENT / CAN_MODE_LOOPBACK / CAN_MODE_SILENT_LOOPBACK + hcan1.Init.TimeTriggeredMode = DISABLE; // TTCAN is used to assign timeslot to the devices for real time applications + hcan1.Init.ReceiveFifoLocked = DISABLE; // Handle RX FIFO overruns. DISABLE: Overwrite previous message with new one. ENABLE: Discard the new message. + hcan1.Init.TransmitFifoPriority = ENABLE; // Handle TX FIFO send order. ENABLE: Chronologically. DISABLE: Transmit lower ID number first. + + status = HAL_CAN_Init(&hcan1); // Calls HAL_CAN_MspInit + if (status != HAL_OK) + return status; + + CAN_FilterTypeDef sFilterConfig; + + // Catch CAN messags with highest bit of StdId set in FIFO0 + sFilterConfig.FilterBank = 0; // This filter bank ID number (0-13 for single CAN instances) + sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // Accept if "Received ID" & Mask = ID, CAN_FILTERMODE_IDMASK / CAN_FILTERMODE_IDLIST (See Figure 342 in RM0090) + sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // CAN_FILTERSCALE_16BIT / CAN_FILTERSCALE_32BIT (See Figure 342 in RM0090) + sFilterConfig.FilterIdHigh = 0b1000000000000000; // ID MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) + sFilterConfig.FilterIdLow = 0; // ID LSB: (0-0xFFFF) ([ExtId12-0][IDE][RTR] 0) (0="don't care") + sFilterConfig.FilterMaskIdHigh = 0b1000000000000000; // Mask MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) + sFilterConfig.FilterMaskIdLow = 0; // Mask LSB: (0-0xFFFF) ([ExtId12-0][IDE][RTR] 0) (0="don't care") + sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; // Store message in FIFO1 (CAN_FILTER_FIFO0 / CAN_FILTER_FIFO1) + sFilterConfig.FilterActivation = CAN_FILTER_ENABLE; // CAN_FILTER_ENABLE / CAN_FILTER_DISABLE + sFilterConfig.SlaveStartFilterBank = 14; // Start bank number for CAN slave instance (not used in single CAN setups) + HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig); + + // Catch all remaining CAN messages in FIFO1 + sFilterConfig.FilterBank = 1; // This filter bank ID number (0-13 for single CAN instances) + sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // CAN_FILTERMODE_IDMASK / CAN_FILTERMODE_IDLIST (See Figure 342 in RM0090) + sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // CAN_FILTERSCALE_16BIT / CAN_FILTERSCALE_32BIT (See Figure 342 in RM0090) + sFilterConfig.FilterIdHigh = 0b0000; // ID MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) + sFilterConfig.FilterIdLow = 0x0000; // ID LSB: (0-0xFFFF) ([ExtId12-0][IDE][RTR] 0) (0="don't care") + sFilterConfig.FilterMaskIdHigh = 0b0000; // Mask MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) + sFilterConfig.FilterMaskIdLow = 0x0000; // Mask LSB: (0-0xFFFF) ([ExtId12-0][IDE][RTR] 0) (0="don't care") + sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO1; // Store message in FIFO0 (CAN_FILTER_FIFO0 / CAN_FILTER_FIFO1) + sFilterConfig.FilterActivation = CAN_FILTER_ENABLE; // CAN_FILTER_ENABLE / CAN_FILTER_DISABLE + sFilterConfig.SlaveStartFilterBank = 14; // Start bank number for CAN slave instance (not used in single CAN setups) + HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig); + + // Activate RX FIFO0/FIFO1 new message interrupt + HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING); // Calls CAN1_RX0_IRQHandler -> HAL_CAN_IRQHandler -> HAL_CAN_RxFifo0MsgPendingCallback + HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO1_MSG_PENDING); // Calls CAN1_RX1_IRQHandler -> HAL_CAN_IRQHandler -> HAL_CAN_RxFifo1MsgPendingCallback + +// Activate RX FIFO0/FIFO1 overrun interrupt + HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_OVERRUN); // Calls CAN1_RX0_IRQHandler -> HAL_CAN_ErrorCallback + HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO1_OVERRUN); // Calls CAN1_RX1_IRQHandler -> HAL_CAN_ErrorCallback + + HAL_CAN_ActivateNotification(&hcan1, CAN_IT_ERROR); // Calls CAN1_RX0_IRQHandler -> HAL_CAN_ErrorCallback + + status = HAL_CAN_Start(&hcan1); // Start the CAN1 module + if (status != HAL_OK) + return status; + + return CAN_Send_Gcode_2params('M', 997, 0, 0, 0, 0); // M997, reset head at host startup +} + +void HAL_CAN_RxFifoMsgPending(CAN_HandleTypeDef *hcan, uint32_t RxFifo) // ISR! New FIFO 0/1 message interrupt handler +{ + CAN_RxHeaderTypeDef RxHeader; // RX Header buffer for FIFO0 + uint8_t CAN_RX_buffer_Fifo[8]; // CAN MESSAGE DATA BUFFER + + if (HAL_CAN_GetRxMessage(&hcan1, RxFifo, &RxHeader, CAN_RX_buffer_Fifo) == HAL_OK) // Get message from CAN_RX_FIFO0 + { + if ((RxHeader.StdId & CAN_IO_MASK) != CAN_io_state) // First handle time critical IO update + { + CAN_io_state = (RxHeader.StdId & CAN_IO_MASK); + endstops.update(); + } + + if (RxHeader.StdId & CAN_STRING_MESSAGE_MASK) // Head sends a string message + { + char * CAN_RX_p = (char *)CAN_RX_buffer_Fifo; + for (uint32_t i = 0; i < RxHeader.DLC; i++) + { + string_message[string_message_index++ % 128] = CAN_RX_p[i]; // Copy message to global buffer + + if (CAN_RX_p[i] == '\n') + { + string_message_complete = true; // Print buffer + string_message[string_message_index % 128] = 0; // Close string with \0 + } + } + } + else + if (RxHeader.DLC == 4) // FIFO0, head sent a temperature update (DLC = Data Length Code == 4 bytes) + { + float * fp = (float *)CAN_RX_buffer_Fifo; // FIFO0 + thermalManager.temp_hotend[0].celsius = *fp; // Set E0 hotend temperature + Last_CAN_Temp_Report = millis(); + FirstE0Error = true; // Reset error status + } + + CAN_head_setup_request = (RxHeader.StdId & CAN_REQUEST_SETUP_MASK) > 0; // FIFO0, head signals request for data + CAN_head_error = (RxHeader.StdId & CAN_ERROR_MASK) > 0; // FIFO0, head signals an error + } +} + +void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) // ISR! New FIFO0 message interrupt handler +{ + HAL_CAN_RxFifoMsgPending(hcan, CAN_RX_FIFO0); +} + +void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan) // ISR! New FIFO1 message interrupt handler +{ + HAL_CAN_RxFifoMsgPending(hcan, CAN_RX_FIFO1); +} + +void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) // Interrupt handler for any CAN error +{ + HAL_CAN_error_code = hcan->ErrorCode; // Store the received error code +} + +/* +CAN Bus Control functions + HAL_StatusTypeDef HAL_CAN_Start(CAN_HandleTypeDef *hcan) Start the CAN module + HAL_StatusTypeDef HAL_CAN_Stop(CAN_HandleTypeDef *hcan) Stop the CAN module + HAL_StatusTypeDef HAL_CAN_RequestSleep(CAN_HandleTypeDef *hcan) Request sleep mode entry. + HAL_StatusTypeDef HAL_CAN_WakeUp(CAN_HandleTypeDef *hcan) Wake up from sleep mode. + uint32_t HAL_CAN_IsSleepActive(CAN_HandleTypeDef *hcan) Check is sleep mode is active. + HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox) Add a message to the Tx mailboxes and activate the corresponding transmission request + HAL_StatusTypeDef HAL_CAN_AbortTxRequest(CAN_HandleTypeDef *hcan, uint32_t TxMailboxes) Abort transmission request + uint32_t HAL_CAN_GetTxMailboxesFreeLevel(CAN_HandleTypeDef *hcan) Return Tx mailboxes free level + uint32_t HAL_CAN_IsTxMessagePending(CAN_HandleTypeDef *hcan, uint32_t TxMailboxes);Check if a transmission request is pending on the selected Tx mailbox + uint32_t HAL_CAN_GetTxTimestamp(CAN_HandleTypeDef *hcan, uint32_t TxMailbox) Return Tx Timestamp + HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[]) Get a CAN frame from the Rx FIFO + uint32_t HAL_CAN_GetRxFifoFillLevel(CAN_HandleTypeDef *hcan, uint32_t RxFifo) Return Rx FIFO fill level + +CAN INTERRUPT FUNCTIONS (See STM32F4xx_hal_can.c) + HAL_StatusTypeDef HAL_CAN_ActivateNotification(CAN_HandleTypeDef *hcan, uint32_t ActiveITs) // Enable interrupts + HAL_StatusTypeDef HAL_CAN_DeactivateNotification(CAN_HandleTypeDef *hcan, uint32_t InactiveITs) // Disable interrupts + +CAN WEAK CALLBACKS WHEN USING STANDARD WEAK CAN1_RX0_IRQHandler------------> INTERRUPTS + __weak void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan) // CAN_IT_TX_MAILBOX_EMPTY + __weak void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef *hcan) // CAN_IT_TX_MAILBOX_EMPTY + __weak void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef *hcan) // CAN_IT_TX_MAILBOX_EMPTY + __weak void HAL_CAN_TxMailbox0AbortCallback(CAN_HandleTypeDef *hcan) // CAN_IT_TX_MAILBOX_EMPTY + __weak void HAL_CAN_TxMailbox1AbortCallback(CAN_HandleTypeDef *hcan) // CAN_IT_TX_MAILBOX_EMPTY + __weak void HAL_CAN_TxMailbox2AbortCallback(CAN_HandleTypeDef *hcan) // CAN_IT_TX_MAILBOX_EMPTY + __weak void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) // CAN_IT_RX_FIFO0_MSG_PENDING + __weak void HAL_CAN_RxFifo0FullCallback(CAN_HandleTypeDef *hcan) // CAN_IT_RX_FIFO0_FULL + __weak void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan) // CAN_IT_RX_FIFO1_MSG_PENDING + __weak void HAL_CAN_RxFifo1FullCallback(CAN_HandleTypeDef *hcan) // CAN_IT_RX_FIFO1_FULL + __weak void HAL_CAN_SleepCallback(CAN_HandleTypeDef *hcan) // CAN_IT_SLEEP_ACK + __weak void HAL_CAN_WakeUpFromRxMsgCallback(CAN_HandleTypeDef *hcan) // CAN_IT_WAKEUP + __weak void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) // CAN_IT_ERROR +*/ + +#endif // CAN_MASTER \ No newline at end of file diff --git a/Marlin/src/HAL/STM32/CAN.h b/Marlin/src/HAL/STM32/CAN.h new file mode 100644 index 000000000000..32dc76a2ec21 --- /dev/null +++ b/Marlin/src/HAL/STM32/CAN.h @@ -0,0 +1,28 @@ +#pragma once + +#include "../../inc/MarlinConfig.h" + +#ifdef CAN_MASTER + + extern uint32_t CAN_io_state; // CAN virtual IO variable + + #define CAN_IO_MASK 0b11111 // Masks for the 5 virtual IO bits + #define CAN_PROBE_MASK 1 // Virtual IO bit + #define CAN_FILAMENT_MASK 2 // Virtual IO bit + #define CAN_X_ENDSTOP_MASK 4 // Virtual IO bit + #define CAN_Y_ENDSTOP_MASK 8 // Virtual IO bit + #define CAN_Z_ENDSTOP_MAS 16 // Virtual IO bit + #define CAN_STRING_MESSAGE_MASK 32 // Signals the head sends a string message + #define CAN_REQUEST_SETUP_MASK 64 // Signals the head requests setup information + #define CAN_TMC_OT_MASK 128 // Signals the head signals a TMC Over Temp error + #define CAN_E0_TARGET_MASK 256 // Signals E0 or E1 + #define CAN_ERROR_MASK 512 // Signals the head encountered an error + + HAL_StatusTypeDef CAN1_Start(void); // FUNCTION PROTOTYPES + HAL_StatusTypeDef CAN1_Stop(void); + HAL_StatusTypeDef CAN_Send_Gcode(void); + HAL_StatusTypeDef CAN_Send_Gcode_2params(uint32_t Gcode_type, uint32_t Gcode_no, uint32_t parameter1, float value1, uint32_t parameter2, float value2); + void CAN_Send_Setup(); // Send host configuration to head + void CAN_Idle(); // Idle CAN task + +#endif // CAN_MASTER \ No newline at end of file diff --git a/Marlin/src/HAL/STM32/Servo.cpp b/Marlin/src/HAL/STM32/Servo.cpp index 4f026ffc6df4..dc6888ff9d11 100644 --- a/Marlin/src/HAL/STM32/Servo.cpp +++ b/Marlin/src/HAL/STM32/Servo.cpp @@ -71,6 +71,21 @@ int8_t libServo::attach(const int pin, const int min, const int max) { } void libServo::move(const int value) { + +#ifdef CAN_MASTER // IRON, FORWARD UNDERWATER SERVO COMMAND TO HEAD + int angles[2] = Z_SERVO_ANGLES; + + // Translate M280 S10 to M401, M280 S90 to M402 + HAL_StatusTypeDef CAN_Send_Gcode_2params(uint32_t Gcode_type, uint32_t Gcode_no, uint32_t parameter1, float value1, uint32_t parameter2, float value2); // PROTOTYPE + if (value == angles[0]) // Deploy angle + CAN_Send_Gcode_2params('M', 401, 0, 0, 0, 0); // IRON, send "M401" instead, enables interrupt etc. + else + if (value == angles[1]) // Stow angle + CAN_Send_Gcode_2params('M', 402, 0, 0, 0, 0); // IRON, send "M401" instead, enables interrupt etc. + else + CAN_Send_Gcode_2params('M', 280, 'S', value, 'P', 0); // M280 S[value] P0 +#endif // IRON + if (attach(0) >= 0) { stm32_servo.write(value); safe_delay(delay); diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index 140423f25c76..3b73fd8b5014 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -34,6 +34,10 @@ #include "HAL/shared/esp_wifi.h" #include "HAL/shared/cpu_exception/exception_hook.h" +#ifdef CAN_MASTER // IRON, ADDED + #include HAL_PATH(., CAN.h) +#endif + #if ENABLED(WIFISUPPORT) #include "HAL/shared/esp_wifi.h" #endif @@ -1190,7 +1194,14 @@ void setup() { while (!MYSERIAL3.connected() && PENDING(millis(), serial_connect_timeout)) { /*nada*/ } #endif #endif - SERIAL_ECHOLNPGM("start"); + SERIAL_ECHOLNPGM("\nstart\n"); // IRON, ADDED \n + +#ifdef CAN_MASTER // IRON, REPORT CAN START STATUS + if (CAN1_Start() == HAL_OK) + SERIAL_ECHOLNPGM(">>> CAN1 Start: OK"); + else + SERIAL_ECHOLNPGM(">>> CAN1 Start: FAILED!"); +#endif // Set up these pins early to prevent suicide #if HAS_KILL diff --git a/Marlin/src/feature/pause.cpp b/Marlin/src/feature/pause.cpp index 3589847761c5..3af6b59709e4 100644 --- a/Marlin/src/feature/pause.cpp +++ b/Marlin/src/feature/pause.cpp @@ -225,8 +225,13 @@ bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load switch (active_extruder) { REPEAT_1(NUM_RUNOUT_SENSORS, _CASE_INSERTED) } + #else + #ifdef CAN_MASTER // IRON, USE VIRTUAL CAN FILAMENT RUNOUT STATUS + if ((!!(CAN_io_state & CAN_FILAMENT_MASK)) != FIL_RUNOUT_STATE) + wait_for_user = false; #else if (!FILAMENT_IS_OUT()) wait_for_user = false; + #endif // IRON #endif #endif idle_no_sleep(); diff --git a/Marlin/src/feature/runout.h b/Marlin/src/feature/runout.h index 52a9020830b7..8ecb94a21b7f 100644 --- a/Marlin/src/feature/runout.h +++ b/Marlin/src/feature/runout.h @@ -33,6 +33,10 @@ #include "pause.h" // for did_pause_print #include "../MarlinCore.h" // for printingIsActive() +#ifdef CAN_MASTER // IRON, ADDED + #include HAL_PATH(.., CAN.h) +#endif + #include "../inc/MarlinConfig.h" #if ENABLED(EXTENSIBLE_UI) @@ -207,7 +211,13 @@ class FilamentSensorBase { // Return a bitmask of runout pin states static uint8_t poll_runout_pins() { + +#ifdef CAN_MASTER // IRON, VIRTUAL FILAMENT RUNOUT PIN + #define _OR_RUNOUT(N) | ((CAN_io_state & CAN_FILAMENT_MASK) ? _BV((N) - 1) : 0) +#else #define _OR_RUNOUT(N) | (READ(FIL_RUNOUT##N##_PIN) ? _BV((N) - 1) : 0) +#endif // IRON + return (0 REPEAT_1(NUM_RUNOUT_SENSORS, _OR_RUNOUT)); #undef _OR_RUNOUT } diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp index 9fed4dcada3f..53d84ac27cb4 100644 --- a/Marlin/src/gcode/gcode.cpp +++ b/Marlin/src/gcode/gcode.cpp @@ -28,6 +28,10 @@ #include "gcode.h" GcodeSuite gcode; +#ifdef CAN_MASTER // IRON + #include HAL_PATH(.., CAN.h) +#endif + #if ENABLED(WIFI_CUSTOM_COMMAND) extern bool wifi_custom_command(char * const command_ptr); #endif @@ -323,6 +327,17 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) { KEEPALIVE_STATE(IN_HANDLER); +#ifdef CAN_MASTER + if (CAN_Send_Gcode() != HAL_OK) // IRON, SEND COMMAND TO HEAD + { + SERIAL_ECHOPGM("Error: FAILED TO SEND GCODE CAN COMMAND TO HEAD: "); + SERIAL_ECHOLN_P(parser.command_ptr); + #ifndef IRON_DEBUG + BUZZ(1, SOUND_ERROR); + #endif + } +#endif // IRON + /** * 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 diff --git a/Marlin/src/gcode/temp/M306.cpp b/Marlin/src/gcode/temp/M306.cpp index 12e175420dc2..57ed2518ff9e 100644 --- a/Marlin/src/gcode/temp/M306.cpp +++ b/Marlin/src/gcode/temp/M306.cpp @@ -27,6 +27,7 @@ #include "../gcode.h" #include "../../lcd/marlinui.h" #include "../../module/temperature.h" +#include "../../libs/numtostr.h" // IRON, ADDED /** * M306: MPC settings and autotune @@ -57,6 +58,13 @@ void GcodeSuite::M306() { #if ENABLED(MPC_AUTOTUNE) if (parser.seen_test('T')) { + +#ifdef CAN_MASTER // IRON, MPC AUTOTUNE INFO + SERIAL_ECHOLNPGM(">>> Forwarding M306 to head board"); + SERIAL_ECHOLNPGM(">>> Store MPC setup in the host Configuration.h or use M500"); + SERIAL_ECHOLNPGM(">>> MPC heater power is: ", ftostr31ns(MPC_HEATER_POWER), " Watt"); + SERIAL_ECHOLNPGM(">>> Please wait for the auto tune results..."); +#else Temperature::MPCTuningType tuning_type; const uint8_t type = parser.byteval('S', 0); switch (type) { @@ -67,6 +75,7 @@ void GcodeSuite::M306() { LCD_MESSAGE(MSG_MPC_AUTOTUNE); thermalManager.MPC_autotune(e, tuning_type); ui.reset_status(); +#endif // IRON return; } #endif @@ -91,6 +100,12 @@ void GcodeSuite::M306_report(const bool forReplay/*=true*/) { TERN_(MARLIN_SMALL_BUILD, return); report_heading(forReplay, F("Model predictive control")); + +#ifdef CAN_MASTER // IRON, 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; diff --git a/Marlin/src/module/endstops.cpp b/Marlin/src/module/endstops.cpp index 85e021d661ea..5ce8f43b3551 100644 --- a/Marlin/src/module/endstops.cpp +++ b/Marlin/src/module/endstops.cpp @@ -78,7 +78,13 @@ Endstops::endstop_mask_t Endstops::live_state = 0; #define READ_ENDSTOP(P) READ(P) #endif #else + +#ifdef CAN_MASTER // IRON, READ VIRTUAL CAN IO PROBE STATUS IF NEEDED + #define READ_ENDSTOP(P) ((P == Z_MIN_PIN) ? ((CAN_io_state & CAN_PROBE_MASK)) : READ(P)) +#else #define READ_ENDSTOP(P) READ(P) +#endif // IRON, ADDED + #endif #if ENDSTOP_NOISE_THRESHOLD @@ -526,7 +532,13 @@ void __O2 Endstops::report_states() { } #undef _CASE_RUNOUT #elif HAS_FILAMENT_SENSOR + + #ifdef CAN_MASTER // IRON + print_es_state((!!(CAN_io_state & CAN_FILAMENT_MASK)) != FIL_RUNOUT1_STATE, F(STR_FILAMENT)); + #else print_es_state(READ(FIL_RUNOUT1_PIN) != FIL_RUNOUT1_STATE, F(STR_FILAMENT)); + #endif // IRON + #endif TERN_(BLTOUCH, bltouch._reset_SW_mode()); diff --git a/Marlin/src/module/probe.h b/Marlin/src/module/probe.h index 08a02b4d4004..e849a28324f5 100644 --- a/Marlin/src/module/probe.h +++ b/Marlin/src/module/probe.h @@ -29,6 +29,10 @@ #include "motion.h" +#ifdef CAN_MASTER // IRON + #include HAL_PATH(.., CAN.h) +#endif + #if ENABLED(BLTOUCH) #include "../feature/bltouch.h" #endif @@ -48,10 +52,23 @@ #if ENABLED(BD_SENSOR) #define PROBE_READ() bdp_state #elif USE_Z_MIN_PROBE - #define PROBE_READ() READ(Z_MIN_PROBE_PIN) + + #ifdef CAN_MASTER + #define PROBE_READ() ((CAN_io_state & CAN_PROBE_MASK)) // IRON, READ VIRTUAL IO + #else + #define PROBE_READ() READ(Z_MIN_PROBE_PIN) + #endif // IRON + #else - #define PROBE_READ() READ(Z_MIN_PIN) -#endif + + #ifdef CAN_MASTER // IRON, VIRTUAL IO + #define PROBE_READ() ((CAN_io_state & CAN_PROBE_BIT)) // IRON, READ VIRTUAL IO + #else + #define PROBE_READ() READ(Z_MIN_PIN) + #endif + +#endif // IRON + #if USE_Z_MIN_PROBE #define PROBE_HIT_STATE Z_MIN_PROBE_ENDSTOP_HIT_STATE #else diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp index 96295d7db586..1f78f4f9ffb5 100644 --- a/Marlin/src/module/temperature.cpp +++ b/Marlin/src/module/temperature.cpp @@ -2734,9 +2734,11 @@ void Temperature::updateTemperaturesFromRawValues() { temp_bed.setraw(read_max_tc_bed()); #endif +#ifndef CAN_MASTER // IRON, NOT FOR MASTER, DON'T READ TEMPERATURE FROM SENSOR, GET IT VIA CAN BUS FROM HEAD #if HAS_HOTEND HOTEND_LOOP() temp_hotend[e].celsius = analog_to_celsius_hotend(temp_hotend[e].getraw(), e); #endif +#endif // IRON TERN_(HAS_HEATED_BED, temp_bed.celsius = analog_to_celsius_bed(temp_bed.getraw())); TERN_(HAS_TEMP_CHAMBER, temp_chamber.celsius = analog_to_celsius_chamber(temp_chamber.getraw())); @@ -2750,6 +2752,9 @@ void Temperature::updateTemperaturesFromRawValues() { TERN_(HAS_POWER_MONITOR, power_monitor.capture_values()); #if HAS_HOTEND + +#ifndef CAN_MASTER // IRON, ONLY FOR HEAD, NO TEMP SAMPLING ON MASTER + #define _TEMPDIR(N) TEMP_SENSOR_IS_ANY_MAX_TC(N) ? 0 : TEMPDIR(N), static constexpr int8_t temp_dir[HOTENDS] = { REPEAT(HOTENDS, _TEMPDIR) }; @@ -2775,6 +2780,7 @@ void Temperature::updateTemperaturesFromRawValues() { TERN_(MULTI_MAX_CONSECUTIVE_LOW_TEMP_ERR, consecutive_low_temperature_error[e] = 0); } } +#endif // IRON, !CAN_MASTER #endif // HAS_HOTEND @@ -3375,6 +3381,13 @@ void Temperature::disable_all_heaters() { TERN_(PROBING_HEATERS_OFF, pause_heaters(false)); #if HAS_HOTEND + +#ifdef CAN_MASTER // IRON, SHUTDOWN HOTEND IN HEAD TOO + CAN_Send_Gcode_2params('M', 104, 'S', 0, 0, 0); // IRON, M104 S0, switch off hotend heating + CAN_Send_Gcode_2params('M', 107, 0, 0, 0, 0); // IRON, M107, switch off part cooling fan + CAN_Send_Gcode_2params('M', 150, 'R', 255, 0, 0); // IRON, M150 R255, SET NEOPIXEL TO RED +#endif + HOTEND_LOOP() { setTargetHotend(0, e); temp_hotend[e].soft_pwm_amount = 0; diff --git a/ini/stm32f4.ini b/ini/stm32f4.ini index 7fdbc6980477..b90b46ae7375 100644 --- a/ini/stm32f4.ini +++ b/ini/stm32f4.ini @@ -619,6 +619,11 @@ build_flags = ${stm_flash_drive.build_flags} ${stm32f4_I2C1_CAN.build_flag extends = env:mks_monster8_usb_flash_drive build_flags = ${env:mks_monster8_usb_flash_drive.build_flags} -DUSBD_USE_CDC_MSC +[env:mks_monster8_usb_flash_drive_msc_CAN] +extends = env:mks_monster8_usb_flash_drive +build_flags = ${env:mks_monster8_usb_flash_drive.build_flags} + -DUSBD_USE_CDC_MSC + -DHAL_CAN_MODULE_ENABLED build_unflags = -DUSBD_USE_CDC # diff --git a/platformio.ini b/platformio.ini index ed1670dc6daf..88e468c49e28 100644 --- a/platformio.ini +++ b/platformio.ini @@ -13,7 +13,7 @@ [platformio] src_dir = Marlin boards_dir = buildroot/share/PlatformIO/boards -default_envs = mega2560 +default_envs = #define MOTHERBOARD BOARD_MKS_MONSTER8_V2 include_dir = Marlin extra_configs = Marlin/config.ini From 264727331fa31c38b41f940390a957ab78a76308 Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Wed, 27 Nov 2024 00:57:09 +0800 Subject: [PATCH 02/21] Configuration.h MOTHERBOARD fix Fix typo in Configuration.,h for MOTHERBOARD --- Marlin/Configuration.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index d4ba2265dd75..35ca67c0f578 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -70,7 +70,7 @@ // Choose the name from boards.h that matches your setup #ifndef MOTHERBOARD - #define MOTHERBOARD #define MOTHERBOARD BOARD_MKS_MONSTER8_V2 + #define MOTHERBOARD BOARD_MKS_MONSTER8_V2 #endif // @section serial From 9508283c77df4a8ea52e93a345693f09aa8fbdc5 Mon Sep 17 00:00:00 2001 From: Scott Lahteine Date: Tue, 26 Nov 2024 15:05:34 -0600 Subject: [PATCH 03/21] clean up --- Marlin/Configuration.h | 7 +- Marlin/src/HAL/STM32/CAN.cpp | 513 +++++++++--------- Marlin/src/HAL/STM32/CAN.h | 28 - Marlin/src/HAL/STM32/Servo.cpp | 27 +- Marlin/src/HAL/shared/CAN.h | 50 ++ Marlin/src/MarlinCore.cpp | 19 +- Marlin/src/feature/pause.cpp | 5 - Marlin/src/feature/runout.h | 18 +- Marlin/src/gcode/gcode.cpp | 25 +- Marlin/src/gcode/temp/M306.cpp | 51 +- Marlin/src/module/endstops.cpp | 23 +- Marlin/src/module/probe.h | 24 +- Marlin/src/module/temperature.cpp | 26 +- Marlin/src/pins/pins.h | 2 +- Marlin/src/pins/stm32g0/pins_BTT_EBB42_V1_1.h | 2 +- ini/stm32f4.ini | 1 + platformio.ini | 2 +- 17 files changed, 418 insertions(+), 405 deletions(-) delete mode 100644 Marlin/src/HAL/STM32/CAN.h create mode 100644 Marlin/src/HAL/shared/CAN.h diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index 35ca67c0f578..2cdffe5179ac 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -37,8 +37,6 @@ */ #define CONFIGURATION_H_VERSION 02010300 -// ENABLE CAN SUPPORT HERE -// #define CAN_MASTER //=========================================================================== //============================= Getting Started ============================= //=========================================================================== @@ -70,7 +68,7 @@ // Choose the name from boards.h that matches your setup #ifndef MOTHERBOARD - #define MOTHERBOARD BOARD_MKS_MONSTER8_V2 + #define MOTHERBOARD BOARD_RAMPS_14_EFB #endif // @section serial @@ -126,6 +124,9 @@ //#define RS485_BUS_BUFFER_SIZE 128 #endif +// Enable CAN bus support and protocol +//#define CAN_MASTER + // Enable the Bluetooth serial interface on AT90USB devices //#define BLUETOOTH diff --git a/Marlin/src/HAL/STM32/CAN.cpp b/Marlin/src/HAL/STM32/CAN.cpp index 52cfeca1d2d7..89292f986d3f 100644 --- a/Marlin/src/HAL/STM32/CAN.cpp +++ b/Marlin/src/HAL/STM32/CAN.cpp @@ -1,63 +1,85 @@ -// IRON NOTES: -// NOTE 1: For MKS Monster 8 V1/V2 on Arduino use: Board "Generic STM32F4 series", Board part number "Generic F407VETx" -// NOTE 2: Make sure to define "HAL_CAN_MODULE_ENABLED". In VSCode add "build_flags = -D HAL_CAN_MODULE_ENABLED" in platformio.ini. -// For Arduino IDE include "hal_conf_extra.h" holding "#define HAL_CAN_MODULE_ENABLED" -// NOTE 3: To accept all CAN messages, enable 1 filter (FilterBank = 0) in "FilterMode = CAN_FILTERMODE_IDMASK", mask and ID = 0 (0=don't care) -// NOTE 4: Serial communication in ISR causes issues! Hangs etc. so avoid this! -// NOTE 5: A FIFO storage cell is called a Mailbox in STM32F4xx, FIFO0 and FiFO1 can hold 3 CAN message each. -// NOTE 6: The filter ID/mask numbers (LOW/HIGH) do not directly relate to the message ID numbers (See Figure 342 in RM0090) - -#include "../../inc/MarlinConfig.h" - -#ifdef CAN_MASTER - #include "../platforms.h" - #include "../../gcode/gcode.h" - #include "../../module/temperature.h" - #include "../../module/motion.h" // For current_position variable - #include "../../module/planner.h" // For steps/mm parameters variables - #include "../../feature/tmc_util.h" - #include "../../module/stepper.h" - #include "../../module/endstops.h" - #include "../../feature/controllerfan.h" // For controllerFan settings - #include "../../libs/numtostr.h" // For float to string conversion - - // #define CAN_DEBUG // Define to show gcodes send to HEAD - - #define CAN_EXTENDED_ID CAN_ID_EXT - #define CAN_STANDARD_ID CAN_ID_STD - - #define STDID_FIFO_BIT 0b10000000000 - #define EXTID_FIFO_BIT 0x10000000 - - #define CAN_IO_MASK 0b11111 // Masks for the 5 virtual IO bits (see below) - #define GCODE_NUMBER_MASK 0b1111111111111 - #define PARAMETER_MASK 0b11111 - #define PARAMETER_COUNT_MASK 0b111 - #define GCODE_TYPE_MASK 0b11 - #define CAN_PROBE_MASK 1 // Virtual IO bit for probe - #define CAN_FILAMENT_MASK 2 // Virtual IO bit for filament - #define CAN_X_ENDSTOP_MASK 4 // Virtual IO bit for X-endstop - #define CAN_Y_ENDSTOP_MASK 8 // Virtual IO bit for Y-endstop - #define CAN_Z_ENDSTOP_MASK 16 // Virtual IO bit for Z-endstop - #define CAN_STRING_MESSAGE_MASK 32 // Signals the head sent a string message - #define CAN_REQUEST_SETUP_MASK 64 // Signals the head requests setup information - #define CAN_TMC_OT_MASK 128 // Signals the head signals a TMC Over Temp error - #define CAN_E0_TARGET_MASK 256 // Signals E0 or E1 - #define CAN_ERROR_MASK 512 // Signals the head encountered an error - - #define PARAMETER1_OFFSET 0 - #define PARAMETER2_OFFSET 5 - #define GCODE_NUMBER_OFFSET 10 - #define GCODE_TYPE_OFFSET 23 - #define PARAMETER_COUNT_OFFSET 25 - - #define GCODE_TYPE_D 0 - #define GCODE_TYPE_G 1 - #define GCODE_TYPE_M 2 - #define GCODE_TYPE_T 3 - -extern "C" void CAN1_RX0_IRQHandler(void); // Override weak CAN FIFO0 interrupt handler -extern "C" void CAN1_RX1_IRQHandler(void); // Override weak CAN FIFO1 interrupt handler +/** + * 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 . + * + */ + +/** + * Contributor Notes: + * NOTE 1: For MKS Monster 8 V1/V2 on Arduino use: Board "Generic STM32F4 series", Board part number "Generic F407VETx" + * NOTE 2: Requires `HAL_CAN_MODULE_ENABLED`, e.g., with `-DHAL_CAN_MODULE_ENABLED` + * For Arduino IDE use "hal_conf_extra.h" with `#define HAL_CAN_MODULE_ENABLED` + * NOTE 3: To accept all CAN messages, enable 1 filter (FilterBank = 0) in "FilterMode = CAN_FILTERMODE_IDMASK", mask and ID = 0 (0=don't care) + * NOTE 4: Serial communication in ISR causes issues! Hangs etc. so avoid this! + * NOTE 5: A FIFO storage cell is called a "Mailbox" in STM32F4xx, FIFO0 and FiFO1 can hold 3 CAN messages each. + * NOTE 6: The filter ID/mask numbers (LOW/HIGH) do not directly relate to the message ID numbers (See Figure 342 in RM0090) + */ + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(CAN_MASTER) + +#include "../platforms.h" +#include "../../gcode/parser.h" +#include "../../module/temperature.h" +#include "../../module/motion.h" // For current_position variable +#include "../../module/planner.h" // For steps/mm parameters variables +#include "../../feature/tmc_util.h" +#include "../../module/endstops.h" +#include "../../feature/controllerfan.h" // For controllerFan settings +#include "../../libs/numtostr.h" // For float to string conversion + +#define CAN_EXTENDED_ID CAN_ID_EXT +#define CAN_STANDARD_ID CAN_ID_STD + +#define STDID_FIFO_BIT 0b10000000000 +#define EXTID_FIFO_BIT 0x10000000 + +#define CAN_IO_MASK 0b11111 // Masks for the 5 virtual IO bits (see below) +#define GCODE_NUMBER_MASK 0b1111111111111 +#define PARAMETER_MASK 0b11111 +#define PARAMETER_COUNT_MASK 0b111 +#define GCODE_TYPE_MASK 0b11 +#define CAN_PROBE_MASK 1 // Virtual IO bit for probe +#define CAN_FILAMENT_MASK 2 // Virtual IO bit for filament +#define CAN_X_ENDSTOP_MASK 4 // Virtual IO bit for X-endstop +#define CAN_Y_ENDSTOP_MASK 8 // Virtual IO bit for Y-endstop +#define CAN_Z_ENDSTOP_MASK 16 // Virtual IO bit for Z-endstop +#define CAN_STRING_MESSAGE_MASK 32 // Signals the head sent a string message +#define CAN_REQUEST_SETUP_MASK 64 // Signals the head requests setup information +#define CAN_TMC_OT_MASK 128 // Signals the head signals a TMC Over Temp error +#define CAN_E0_TARGET_MASK 256 // Signals E0 or E1 +#define CAN_ERROR_MASK 512 // Signals the head encountered an error + +#define PARAMETER1_OFFSET 0 +#define PARAMETER2_OFFSET 5 +#define GCODE_NUMBER_OFFSET 10 +#define GCODE_TYPE_OFFSET 23 +#define PARAMETER_COUNT_OFFSET 25 + +#define GCODE_TYPE_D 0 +#define GCODE_TYPE_G 1 +#define GCODE_TYPE_M 2 +#define GCODE_TYPE_T 3 + +extern "C" void CAN1_RX0_IRQHandler(); // Override weak CAN FIFO0 interrupt handler +extern "C" void CAN1_RX1_IRQHandler(); // Override weak CAN FIFO1 interrupt handler extern "C" void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan); // Override weak CAN interrupt callback for FIFO0 extern "C" void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan); // Override weak CAN interrupt callback for FIFO1 extern "C" void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan); // Override weak CAN interrupt callback @@ -73,71 +95,70 @@ volatile uint32_t HAL_CAN_error_code = 0; // Record a host CAN error messa volatile bool FirstE0Error = true; // First CAN bus error, show warning only once volatile bool string_message_complete = false; // Signals a complete string message was received volatile uint32_t string_message_index = 0; // Index into the CAN string that is being received -uint32_t Last_CAN_Temp_Report = 0; // Track when the last head temperature report was received +uint32_t Next_CAN_Temp_Report = 0; // Track when the next head temperature report will be received uint32_t Last_CAN_Error_Message = 0; // Track when the last CAN error messaget was shown char string_message[128] = "\0"; // CAN string message buffer for incoming message, max 128 characters -void CAN1_RX0_IRQHandler(void) // CAN FIFO0 Interrupt handler overrides standard weak CAN1_RX0_IRQHandler -{ +void CAN1_RX0_IRQHandler() { // CAN FIFO0 Interrupt handler overrides standard weak CAN1_RX0_IRQHandler HAL_CAN_IRQHandler(&hcan1); // Forwarded for callbacks --> HAL_CAN_RxFifo0MsgPendingCallback/HAL_CAN_ErrorCallback -// OR -// HAL_CAN_RxFifo0MsgPendingCallback(&hcan1); // Call the required callback directly, faster but no error reporting + // OR + //HAL_CAN_RxFifo0MsgPendingCallback(&hcan1); // Call the required callback directly, faster but no error reporting } -void CAN1_RX1_IRQHandler(void) // CAN FIFO1 Interrupt handler overrides standard weak CAN1_RX0_IRQHandler -{ +void CAN1_RX1_IRQHandler() { // CAN FIFO1 Interrupt handler overrides standard weak CAN1_RX0_IRQHandler HAL_CAN_IRQHandler(&hcan1); // Forwarded for callbacks --> HAL_CAN_RxFifo1MsgPendingCallback/HAL_CAN_ErrorCallback -// OR -// HAL_CAN_RxFifo1MsgPendingCallback(&hcan1); // Call the required callback directly, faster but no error reporting + // OR + //HAL_CAN_RxFifo1MsgPendingCallback(&hcan1); // Call the required callback directly, faster but no error reporting } // Send specified Gcode with max 2 parameters and 2 values via CAN bus -HAL_StatusTypeDef CAN_Send_Gcode_2params(uint32_t Gcode_type, uint32_t Gcode_no, uint32_t parameter1, float value1, uint32_t parameter2, float value2) -{ - switch (Gcode_type) - { - case 'D': Gcode_type = GCODE_TYPE_D; - break; - - case 'G': Gcode_type = GCODE_TYPE_G; - break; - - case 'M': Gcode_type = GCODE_TYPE_M; +HAL_StatusTypeDef CAN_Send_Gcode_2params(uint32_t Gcode_type, uint32_t Gcode_no, uint32_t parameter1, float value1, uint32_t parameter2, float value2) { + switch (Gcode_type) { + case 'D': + Gcode_type = GCODE_TYPE_D; + break; -#ifdef CAN_DEBUG - SERIAL_ECHOPGM(">>> CAN TO HEAD: M", Gcode_no); - if (parameter1) { - SERIAL_CHAR(' ', parameter1); - if (value1 == int(value1)) - SERIAL_ECHO(i16tostr3left(value1)); // Integer value - else - SERIAL_ECHO(p_float_t(value1, 4)); // Float with 4 digits - } + case 'G': + Gcode_type = GCODE_TYPE_G; + break; - if (parameter2) { - SERIAL_CHAR(' ', parameter2); - if (value2 == int(value2)) - SERIAL_ECHO(i16tostr3left(value2)); // Integer value - else - SERIAL_ECHO(p_float_t(value2, 4)); // Float with 4 digits - } - SERIAL_EOL(); -#endif + case 'M': + Gcode_type = GCODE_TYPE_M; + + #ifdef CAN_DEBUG + SERIAL_ECHOPGM(">>> CAN TO HEAD: M", Gcode_no); + if (parameter1) { + SERIAL_CHAR(' ', parameter1); + if (value1 == int(value1)) + SERIAL_ECHO(i16tostr3left(value1)); // Integer value + else + SERIAL_ECHO(p_float_t(value1, 4)); // Float with 4 digits + } + + if (parameter2) { + SERIAL_CHAR(' ', parameter2); + if (value2 == int(value2)) + SERIAL_ECHO(i16tostr3left(value2)); // Integer value + else + SERIAL_ECHO(p_float_t(value2, 4)); // Float with 4 digits + } + SERIAL_EOL(); + #endif // CAN_DEBUG + + break; - break; - case 'T': Gcode_type = GCODE_TYPE_T; - break; - + break; + default: return HAL_ERROR; // UNKNOWN GCODE TYPE } HAL_StatusTypeDef status = HAL_OK; - uint32_t TxMailbox; // Stores which Mailbox (0-2) is used to store the Sent TX message + uint32_t TxMailbox; // Stores which Mailbox (0-2) is used to store the Sent TX message if (parameter1 > 31) parameter1 -= 64; // Format 'A' = 1, 'B' = 2, etc. - + if (parameter2 > 31) parameter2 -= 64; // Format 'A' = 1, 'B' = 2, etc. @@ -146,18 +167,18 @@ HAL_StatusTypeDef CAN_Send_Gcode_2params(uint32_t Gcode_type, uint32_t Gcode_no, TxHeader.StdId = (TxHeader.StdId ^ STDID_FIFO_BIT); // Toggle FIFO bit 10, keep FIFO toggling in sync TxHeader.ExtId = ((TxHeader.ExtId ^ EXTID_FIFO_BIT) & EXTID_FIFO_BIT) + // Toggle FIFO bit 28 ((TxHeader.DLC >> 2) << PARAMETER_COUNT_OFFSET) + // Data bytes (4 or 8) - (Gcode_type << GCODE_TYPE_OFFSET) + // G/M/T/D-code + (Gcode_type << GCODE_TYPE_OFFSET) + // G/M/T/D-code ((Gcode_no & GCODE_NUMBER_MASK) << GCODE_NUMBER_OFFSET) + // Gcode number ((parameter2 & PARAMETER_MASK) << PARAMETER2_OFFSET) + // First parameter - ((parameter1 & PARAMETER_MASK) << PARAMETER1_OFFSET); // Second parameter + ((parameter1 & PARAMETER_MASK) << PARAMETER1_OFFSET); // Second parameter uint8_t CAN_tx_buffer[8]; // 8 bytes CAN data TX buffer - float * fp = (float *)CAN_tx_buffer; // Point to TX buffer + float * fp = (float *)CAN_tx_buffer; // Point to TX buffer *fp++ = value1; *fp-- = value2; - - uint32_t ms = millis(); // Don't send too fast! + + const uint32_t ms = millis(); // Don't send too fast! while ((HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) == 0) && (millis() - ms < 25)) { } // BLOCKING! Wait max 25ms! - // SERIAL_ECHOLNPGM(">>> Waited1: ", millis() - ms, " FreeTX: ", HAL_CAN_GetTxMailboxesFreeLevel(&hcan1)); // IRON, DEBUGGING + //SERIAL_ECHOLNPGM(">>> Waited1: ", millis() - ms, " FreeTX: ", HAL_CAN_GetTxMailboxesFreeLevel(&hcan1)); // IRON, DEBUGGING status = HAL_CAN_AddTxMessage(&hcan1, &TxHeader, CAN_tx_buffer, &TxMailbox); // Send message if (status == HAL_OK) // Count sent gcode messages @@ -166,25 +187,26 @@ HAL_StatusTypeDef CAN_Send_Gcode_2params(uint32_t Gcode_type, uint32_t Gcode_no, return status; } -HAL_StatusTypeDef CAN_Send_Setup() // Send setup to HEAD -{ +HAL_StatusTypeDef CAN_Send_Setup() { // Send setup to HEAD CAN_head_setup_request = false; SERIAL_ECHOLNPGM(">>> CAN: SENDING CONFIG TO HEAD ====="); -// NOTE: Sending many command too fast will cause a Marlin command buffer overrun at the head, add delays if needed -// CAN_Send_Gcode_2params('M', 104, 'S', 0, 0, 0); // M104 S0, switch off hotend heating, NOT NEEDED ANYMORE -// CAN_Send_Gcode_2params('M', 107, 0, 0, 0, 0); // M107, switch off cooling fan, NOT NEEDED ANYMORE -// CAN_Send_Gcode_2params('M', 18, 0, 0, 0, 0); // M18, switch off steppers, NOT NEEDED ANYMORE - - // M306 MPC settings (managed by host) - MPC_t &mpc = thermalManager.temp_hotend[0].mpc; - - CAN_Send_Gcode_2params('M', 306, 'A', mpc.ambient_xfer_coeff_fan0, 'C', mpc.block_heat_capacity); // M306 R A - CAN_Send_Gcode_2params('M', 306, 'F', mpc.fanCoefficient(), 'H', mpc.filament_heat_capacity_permm); // M306 F H - CAN_Send_Gcode_2params('M', 306, 'P', mpc.heater_power, 'R', mpc.sensor_responsiveness); // M306 P C + // NOTE: Sending many command too fast will cause a Marlin command buffer overrun at the head, add delays if needed + //CAN_Send_Gcode_2params('M', 104, 'S', 0, 0, 0); // M104 S0, switch off hotend heating, NOT NEEDED ANYMORE + //CAN_Send_Gcode_2params('M', 107, 0, 0, 0, 0); // M107, switch off cooling fan, NOT NEEDED ANYMORE + //CAN_Send_Gcode_2params('M', 18, 0, 0, 0, 0); // M18, switch off steppers, NOT NEEDED ANYMORE + + #if ENABLED(MPCTEMP) + // M306 MPC settings (managed by host) + MPC_t &mpc = thermalManager.temp_hotend[0].mpc; + + CAN_Send_Gcode_2params('M', 306, 'A', mpc.ambient_xfer_coeff_fan0, 'C', mpc.block_heat_capacity); // M306 R A + CAN_Send_Gcode_2params('M', 306, 'F', mpc.fanCoefficient(), 'H', mpc.filament_heat_capacity_permm); // M306 F H + CAN_Send_Gcode_2params('M', 306, 'P', mpc.heater_power, 'R', mpc.sensor_responsiveness); // M306 P C + #endif -// CAN_Send_Gcode_2params('M', 150, 0, 0, 0, 0); // M150, SWITCH NEOPIXEL OFF + //CAN_Send_Gcode_2params('M', 150, 0, 0, 0, 0); // M150, SWITCH NEOPIXEL OFF -/* + /* extern Planner planner; // M92 Steps per mm CAN_Send_Gcode_2params('M', 92, 'X', planner.settings.axis_steps_per_mm[X_AXIS], 'Y', planner.settings.axis_steps_per_mm[Y_AXIS]); CAN_Send_Gcode_2params('M', 92, 'Z', planner.settings.axis_steps_per_mm[Z_AXIS], 'E', planner.settings.axis_steps_per_mm[E_AXIS]); @@ -198,7 +220,7 @@ HAL_StatusTypeDef CAN_Send_Setup() // Send setup to HEAD // M201 Max acceleration CAN_Send_Gcode_2params('M', 201, 'X', planner.settings.max_acceleration_mm_per_s2[X_AXIS], 'Y', planner.settings.max_acceleration_mm_per_s2[Y_AXIS]); CAN_Send_Gcode_2params('M', 201, 'Z', planner.settings.max_acceleration_mm_per_s2[Z_AXIS], 'E', planner.settings.max_acceleration_mm_per_s2[E_AXIS]); - + // M203 Max feedrate CAN_Send_Gcode_2params('M', 203, 'X', planner.settings.max_feedrate_mm_s[X_AXIS], 'Y', planner.settings.max_feedrate_mm_s[Y_AXIS]); CAN_Send_Gcode_2params('M', 203, 'Z', planner.settings.max_feedrate_mm_s[Z_AXIS], 'E', planner.settings.max_feedrate_mm_s[E_AXIS]); @@ -208,16 +230,16 @@ HAL_StatusTypeDef CAN_Send_Setup() // Send setup to HEAD CAN_Send_Gcode_2params('M', 204, 'T', planner.settings.travel_acceleration, 0, 0); // M205 - #ifdef CLASSIC_JERK + #if ENABLED(CLASSIC_JERK) CAN_Send_Gcode_2params('M', 205,'S')) planner.settings.min_feedrate_mm_s, 'T')) planner.settings.min_travel_feedrate_mm_s); CAN_Send_Gcode_2params('M', 205, M205_MIN_SEG_TIME_PARAM, planner.settings.min_segment_time_us, 'J', planner.junction_deviation_mm); CAN_Send_Gcode_2params('M', 205, 'X', LINEAR_UNIT(planner.max_jerk.x), 'Y', LINEAR_UNIT(planner.max_jerk.y)); CAN_Send_Gcode_2params('M', 205, 'Z', LINEAR_UNIT(planner.max_jerk.z), 'E', LINEAR_UNIT(planner.max_jerk.e)); - CAN_Send_Gcode_2params('M', 205, 'J', LINEAR_UNIT(planner.junction_deviation_mm), 0, 0); + CAN_Send_Gcode_2params('M', 205, 'J', LINEAR_UNIT(planner.junction_deviation_mm), 0, 0); #endif // M206 Home offset - #ifndef NO_HOME_OFFSETS + #if DISABLED(NO_HOME_OFFSETS) _CAN_Send_Gcode_2params('M', 206, 'X', LINEAR_UNIT(home_offset.x), 'Y', LINEAR_UNIT(home_offset.y)); CAN_Send_Gcode_2params('M', 206, 'Z', LINEAR_UNIT(home_offset.z), 0, 0); #endif @@ -225,14 +247,14 @@ HAL_StatusTypeDef CAN_Send_Setup() // Send setup to HEAD // M207 Set Firmware Retraction // M208 - Firmware Recover // M209 - Set Auto Retract - + // M220 Speed/feedrate CAN_Send_Gcode_2params('M', 220, 'S', feedrate_percentage, 0, 0); - + // M221 Flow percentage CAN_Send_Gcode_2params('M', 221, 'T', 0, 'S', planner.flow_percentage[0]); // CAN_Send_Gcode_2params('M', 221, 'T', 1, 'S', planner.flow_percentage[1]); // For 2nd extruder - + // M302 Cold extrude settings #if ENABLED(PREVENT_COLD_EXTRUSION) CAN_Send_Gcode_2params('M', 302, 'P', '0' + thermalManager.allow_cold_extrude, 'S', thermalManager.extrude_min_temp); // P0 enable cold extrusion checking, P1 = disabled, S=Minimum temperature @@ -249,31 +271,32 @@ HAL_StatusTypeDef CAN_Send_Setup() // Send setup to HEAD // M919 TMC Chopper timing for E only CAN_Send_Gcode_2params('M', 919, 'O', off, 'P' , Hysteresis End); CAN_Send_Gcode_2params('M', 919, 'S', Hysteresis Start, 0, 0); -*/ -/* -CAN_Send_Gcode_2params('M', 710, 'E', 1, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P -CAN_Send_Gcode_2params('M', 710, 'E', 2, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P -CAN_Send_Gcode_2params('M', 710, 'E', 3, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P -CAN_Send_Gcode_2params('M', 710, 'E', 4, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P -CAN_Send_Gcode_2params('M', 710, 'E', 5, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P -CAN_Send_Gcode_2params('M', 710, 'E', 6, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P -CAN_Send_Gcode_2params('M', 710, 'E', 7, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P -CAN_Send_Gcode_2params('M', 710, 'E', 8, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P -CAN_Send_Gcode_2params('M', 710, 'E', 9, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P -CAN_Send_Gcode_2params('M', 710, 'E', 10, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P -CAN_Send_Gcode_2params('M', 710, 'E', 11, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P -*/ - // IRON, M710 SIGNALS TO THE HEAD THAT THE CAN CONFIGURATION IS COMPLETE, USE IT AS THE LAST GCODE TO SEND - return CAN_Send_Gcode_2params('M', 710, 'E', int(controllerFan.settings.extruder_auto_fan_speed), 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P + */ + #if USE_CONTROLLER_FAN + /* + CAN_Send_Gcode_2params('M', 710, 'E', 1, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P + CAN_Send_Gcode_2params('M', 710, 'E', 2, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P + CAN_Send_Gcode_2params('M', 710, 'E', 3, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P + CAN_Send_Gcode_2params('M', 710, 'E', 4, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P + CAN_Send_Gcode_2params('M', 710, 'E', 5, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P + CAN_Send_Gcode_2params('M', 710, 'E', 6, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P + CAN_Send_Gcode_2params('M', 710, 'E', 7, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P + CAN_Send_Gcode_2params('M', 710, 'E', 8, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P + CAN_Send_Gcode_2params('M', 710, 'E', 9, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P + CAN_Send_Gcode_2params('M', 710, 'E', 10, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P + CAN_Send_Gcode_2params('M', 710, 'E', 11, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P + */ + // IRON, M710 SIGNALS TO THE HEAD THAT THE CAN CONFIGURATION IS COMPLETE, USE IT AS THE LAST GCODE TO SEND + return CAN_Send_Gcode_2params('M', 710, 'E', int(controllerFan.settings.extruder_auto_fan_speed), 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P + #endif + return HAL_OK; } -void CAN_Idle() // Tasks that cannot be done in the ISR -{ +void CAN_Idle() { // Tasks that cannot be done in the ISR if (CAN_head_setup_request) // The head requested the setup data CAN_Send_Setup(); - - if (string_message_complete) // Received string message is complete - { + + if (string_message_complete) { // Received string message is complete BUZZ(1, SOUND_OK); SERIAL_ECHOPGM(">>> CAN MSG FROM HEAD: "); for (uint32_t i = 0; i < string_message_index; i++) @@ -283,15 +306,13 @@ void CAN_Idle() // Tasks that cannot be done in the ISR string_message_index = 0; } - if ((hcan1.ErrorCode || CAN_head_error || HAL_CAN_error_code) && (millis() - Last_CAN_Error_Message > 6000)) - { - BUZZ(1, SOUND_ERROR); // Warn with sound - if (CAN_head_error) - SERIAL_ECHOLNPGM(">>> CAN ERROR REPORTED BY HEAD"); + if ((hcan1.ErrorCode || CAN_head_error || HAL_CAN_error_code) && (millis() - Last_CAN_Error_Message > 6000)) { + BUZZ(1, SOUND_ERROR); + if (CAN_head_error) SERIAL_ECHOLNPGM(">>> CAN Error reported by head"); CAN_head_error = false; // Reset, but will be repeated by the head if (HAL_CAN_error_code) - SERIAL_ECHOLNPGM(">>> HAL CAN ERROR REPORTED: ", HAL_CAN_error_code); + SERIAL_ECHOLNPGM(">>> HAL CAN Error reported: ", HAL_CAN_error_code); if (hcan1.ErrorCode) SERIAL_ECHOLNPGM(">>> hcan1.ErrorCode=", hcan1.ErrorCode); @@ -299,27 +320,25 @@ void CAN_Idle() // Tasks that cannot be done in the ISR Last_CAN_Error_Message = millis(); } - if ((millis() - Last_CAN_Temp_Report) > 10000) // IRON, ERROR, NO TEMP UPDATE RECEIVED IN 10 SECONDS, KILL PRINTER - { - Last_CAN_Temp_Report = millis(); - if (FirstE0Error) // Send error notification - { + if (ELAPSED(millis(), Next_CAN_Temp_Report)) { // IRON, ERROR, NO TEMP UPDATE RECEIVED IN 10 SECONDS, KILL PRINTER + Next_CAN_Temp_Report = millis() + 10000; + if (FirstE0Error) { // Send error notification BUZZ(1, SOUND_ERROR); // Warn with sound - SERIAL_ECHOLNPGM("Error: NO CAN E0 TEMP UPDATES!"); + SERIAL_ECHOLNPGM("Error: No CAN E0 temp updates!"); } else // Send only error message - SERIAL_ECHOLNPGM(">>> CAN ERROR, NO E0 TEMP UPDATES!"); + SERIAL_ECHOLNPGM(">>> CAN ERROR, No E0 temp updates!"); FirstE0Error = false; // Warn only once - #ifndef IRON_DEBUG // Only kill if not debugging - kill(F("CAN ERROR, NO E0 TEMPERATURE UPDATES")); - #endif + #ifndef CAN_DEBUG // Only kill if not debugging + kill(F("CAN ERROR, NO E0 TEMPERATURE UPDATES")); + #endif } } -HAL_StatusTypeDef CAN_Send_Gcode() // Forward a Marlin Gcode via CAN (uses parser.command_letter, Gcode_no, parser.value_float()) -{ // Send a Gcode to the head with parameters and values +HAL_StatusTypeDef CAN_Send_Gcode() { // Forward a Marlin Gcode via CAN (uses parser.command_letter, Gcode_no, parser.value_float()) + // Send a Gcode to the head with parameters and values // Gcode starts with extended frame which can send the Gcode with max 2 parameters and values. // Extended frames are send to complete all parameters and values (max 2 per extended message). // 1. Analyze Gcode command @@ -328,25 +347,25 @@ HAL_StatusTypeDef CAN_Send_Gcode() // Forward a Marlin Gcode via CAN (uses parse // char s[] = "G0 X123.45678 Y124.45678 Z125.45678 E126.45678 F127.45678\n"; HAL_StatusTypeDef status = HAL_OK; - uint32_t TxMailbox; // Stores which Mailbox (0-2) is used to store the Sent TX message - + uint32_t TxMailbox; // Stores which Mailbox (0-2) is used to store the Sent TX message + if (parser.command_letter != 'M') // Only forward Mxxx Gcode to head return HAL_OK; - + uint32_t Gcode_type = GCODE_TYPE_M; // M-code, fixed for now uint32_t Gcode_no = parser.codenum; if (Gcode_no == 109) // Convert M109(Hotend wait) to M104 (no wait) to keep the head responsive Gcode_no = 104; - + if ((Gcode_no == 501) || (Gcode_no == 502)) // M501=Restore settings, M502=Factory defaults CAN_head_setup_request = true; // Also update settings for the head if ((Gcode_no != 104) && // Set hotend target temp (Gcode_no != 106) && // Set cooling fan speed (Gcode_no != 107) && // Cooling fan off - (Gcode_no != 150) && // Set NeoPixel values -// (Gcode_no != 108) && // Break and Continue + (Gcode_no != 150) && // Set NeoPixel values + //(Gcode_no != 108) && // Break and Continue (Gcode_no != 280) && // Servo position (Gcode_no != 306) && // MPC settings/tuning (Gcode_no != 710) && // Control fan PWM @@ -414,7 +433,7 @@ HAL_StatusTypeDef CAN_Send_Gcode() // Forward a Marlin Gcode via CAN (uses parse case 510: case 511: case 512: // No locking of the machine case 605: // No IDEX commands case 810: case 811: case 812: case 813: case 814: case 815: case 816: case 817: case 818: case 819: - case 851: // + case 851: // case 871: // No Probe temp config case 876: // No handle prompt response case 913: // No Set Hybrid Threshold Speed @@ -424,7 +443,7 @@ HAL_StatusTypeDef CAN_Send_Gcode() // Forward a Marlin Gcode via CAN (uses parse return HAL_OK; // NO CAM MESSAGE } break; - + case 'T': Gcode_type = GCODE_TYPE_T; switch (Gcode_no) { @@ -432,7 +451,7 @@ HAL_StatusTypeDef CAN_Send_Gcode() // Forward a Marlin Gcode via CAN (uses parse break; } break; - + case 'D': Gcode_type = GCODE_TYPE_D; switch (Gcode_no) { @@ -442,7 +461,7 @@ HAL_StatusTypeDef CAN_Send_Gcode() // Forward a Marlin Gcode via CAN (uses parse break; default: return HAL_OK; // Invalid command, nothing to do } -*/ + */ #ifdef CAN_DEBUG SERIAL_ECHOPGM(">>> CAN GCODE TO HEAD: "); // IRON, DEBUGGING @@ -451,37 +470,32 @@ HAL_StatusTypeDef CAN_Send_Gcode() // Forward a Marlin Gcode via CAN (uses parse #endif if (strlen(parser.command_ptr) > 4) // "M107\0", ONLY SCAN FOR PARAMETERS IF STRING IS LONG ENOUGH - for (index = 0; index < sizeof(letters); index++) // Scan parameters - { - if (parser.seen(letters[index])) - { + for (index = 0; index < sizeof(letters); index++) { // Scan parameters + if (parser.seen(letters[index])) { parameters[parameter_counter] = letters[index] - 64; // Store parameter letter, A=1, B=2... - - #ifdef CAN_DEBUG - SERIAL_CHAR(' ', letters[index]); // IRON, DEBUGGING - #endif - if (parser.has_value()) // Check if there is a value - { + #ifdef CAN_DEBUG + SERIAL_CHAR(' ', letters[index]); // IRON, DEBUGGING + #endif + + if (parser.has_value()) { // Check if there is a value values[parameter_counter++] = parser.value_float(); - #ifdef CAN_DEBUG - if (values[parameter_counter - 1] == int(values[parameter_counter - 1])) - SERIAL_ECHO(i16tostr3left(values[parameter_counter - 1])); // Integer value - else - SERIAL_ECHO(p_float_t(values[parameter_counter - 1], 4)); // Float with 4 digits - #endif + #ifdef CAN_DEBUG + if (values[parameter_counter - 1] == int(values[parameter_counter - 1])) + SERIAL_ECHO(i16tostr3left(values[parameter_counter - 1])); // Integer value + else + SERIAL_ECHO(p_float_t(values[parameter_counter - 1], 4)); // Float with 4 digits + #endif } else // No value for parameter - values[parameter_counter++] = NAN; // Not A Number, indicates no parameter value is present + values[parameter_counter++] = NAN; // Not A Number, indicates no parameter value is present } - - if (parameter_counter == 8) - { + + if (parameter_counter == 8) { // Max 8 parameters parameter_counter--; // Max is 7 parameters - SERIAL_ECHOPGM("\nError: TOO MANY PARAMETERS (> 7): "); - SERIAL_ECHOLN_P(parser.command_ptr); + SERIAL_ECHOLNPGM("\nError: TOO MANY PARAMETERS (> 7): ", parser.command_ptr); BUZZ(1, SOUND_ERROR); break; } @@ -492,13 +506,13 @@ HAL_StatusTypeDef CAN_Send_Gcode() // Forward a Marlin Gcode via CAN (uses parse parameters[parameter_counter] = 0; // Set next parameter to 0 (0=no parameter), send in pairs index = 0; - float * fp = (float *)CAN_tx_buffer; // Points to TX buffer - + float * fp = (float *)CAN_tx_buffer; // Points to TX buffer + if ((Gcode_no == 710) && (parameters[0] == 3)) // "M710 C" INDICATES REQUEST FOR GCODE COUNT SERIAL_ECHOLNPGM(">>> GCODES SENT: ", gcode_counter); - - gcode_counter++; - + + gcode_counter++; + TxHeader.IDE = CAN_EXTENDED_ID; // Start with EXTENDED_ID then send STANDARD_ID if needed //TxHeader.ExtId &= EXTID_FIFO_BIT; // Clear ID, keep FIFO bit TxHeader.ExtId = (TxHeader.ExtId & EXTID_FIFO_BIT) + // KEEP FIFO BIT @@ -508,8 +522,7 @@ HAL_StatusTypeDef CAN_Send_Gcode() // Forward a Marlin Gcode via CAN (uses parse ((parameters[1] & PARAMETER_MASK) << PARAMETER2_OFFSET) + // PARAMETER2 ((parameters[0] & PARAMETER_MASK) << PARAMETER1_OFFSET); // PARAMETER1 uint32_t ms = millis(); // Record message send start time - do - { + do { TxHeader.DLC = MIN(8, (parameter_counter - index) << 2); // Maximum 8 bytes, 4 bytes if there is only 1 parameter, 8 bytes if there are 2 //TxHeader.StdId = (TxHeader.StdId ^ STDID_FIFO_BIT) & STDID_FIFO_BIT; // Toggle FIFO bit 10, clear other bits TxHeader.StdId = ((TxHeader.StdId ^ STDID_FIFO_BIT) & STDID_FIFO_BIT) + // Toggle FIFO bit @@ -518,32 +531,29 @@ HAL_StatusTypeDef CAN_Send_Gcode() // Forward a Marlin Gcode via CAN (uses parse TxHeader.ExtId ^= EXTID_FIFO_BIT; // Toggle FIFO bit 28 *fp++ = values[index++]; // Copy first parameter value to data, move pointer to next 4 bytes *fp-- = values[index++]; // Copy 2nd parameter value to data, move pointer to beginning of data array for next round - + while ((HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) == 0) && (millis() - ms < 50)) { } // BLOCKING! Wait max 50ms status = HAL_CAN_AddTxMessage(&hcan1, &TxHeader, CAN_tx_buffer, &TxMailbox); // Send message if (status != HAL_OK) return status; - + TxHeader.IDE = CAN_STANDARD_ID; // All following messages have standard ID for parameter values, 11 bits identifier } while (index < parameter_counter); - + return HAL_OK; } -void CAN_Send_Position() // Send the X, Y, Z and E position to the HEAD -{ +void CAN_Send_Position() { // Send the X, Y, Z and E position to the HEAD CAN_Send_Gcode_2params('G', 92, 'X', current_position.x, 'Y', current_position.y); // M92 X Y CAN_Send_Gcode_2params('G', 92, 'Z', current_position.z, 'E', current_position.e); // M92 E Z } -void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle) // Called by HAL_CAN_Init -{ +void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle) { // Called by HAL_CAN_Init GPIO_InitTypeDef GPIO_InitStruct = {0}; RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; - if (canHandle->Instance == CAN1) - { + if (canHandle->Instance == CAN1) { RCC_PeriphCLKInitTypeDef periphClkInit = { }; HAL_RCCEx_GetPeriphCLKConfig(&periphClkInit); periphClkInit.PeriphClockSelection |= RCC_APB1ENR_CAN1EN; // DONE IN __HAL_RCC_CAN1_CLK_ENABLE? @@ -560,7 +570,7 @@ void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle) // Called by HAL_CAN_Init // CAN1 GPIO Configuration // PB8 ------> CAN1_RX // PB9 ------> CAN1_TX - + GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9; // Pin PB8 and Pin PB9 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; @@ -569,21 +579,19 @@ void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle) // Called by HAL_CAN_Init HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // Port B // CAN1 interrupt Init - HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 1); + HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 1); HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn); // Enable CAN FIFO1 interrupt handler - HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 1, 1); + HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 1, 1); HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn); // Enable CAN FIFO1 interrupt handler } } -HAL_StatusTypeDef CAN1_Stop(void) -{ +HAL_StatusTypeDef CAN1_Stop() { return HAL_CAN_Stop(&hcan1); } -HAL_StatusTypeDef CAN1_Start(void) -{ +HAL_StatusTypeDef CAN1_Start() { HAL_StatusTypeDef status = HAL_OK; // Init TxHeader with constant values @@ -601,7 +609,7 @@ HAL_StatusTypeDef CAN1_Start(void) hcan1.Init.AutoBusOff = DISABLE; // DISABLE: Software controlled Bus-off. ENABLE: Automatic hardware controlled (no send/receive) hcan1.Init.AutoWakeUp = ENABLE; // ENABLE: Automatic hardware controlled bus wakeup. DISABLE: Software controlled bus wakeup. hcan1.Init.AutoRetransmission = ENABLE; // DISABLE / ENABLE, resend if transmission failed, but locks up if communication fails/cable not connected!!!!!!!!!!!!!!!!! - hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ; // CAN_SJW_1TQ + hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ; // CAN_SJW_1TQ hcan1.Init.TimeSeg1 = CAN_BS1_11TQ; // CAN_BS1_11TQ hcan1.Init.TimeSeg2 = CAN_BS2_2TQ; // CAN_BS2_2TQ hcan1.Init.Mode = CAN_MODE_NORMAL; // CAN_MODE_NORMAL / CAN_MODE_SILENT / CAN_MODE_LOOPBACK / CAN_MODE_SILENT_LOOPBACK @@ -612,7 +620,7 @@ HAL_StatusTypeDef CAN1_Start(void) status = HAL_CAN_Init(&hcan1); // Calls HAL_CAN_MspInit if (status != HAL_OK) return status; - + CAN_FilterTypeDef sFilterConfig; // Catch CAN messags with highest bit of StdId set in FIFO0 @@ -621,7 +629,7 @@ HAL_StatusTypeDef CAN1_Start(void) sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // CAN_FILTERSCALE_16BIT / CAN_FILTERSCALE_32BIT (See Figure 342 in RM0090) sFilterConfig.FilterIdHigh = 0b1000000000000000; // ID MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) sFilterConfig.FilterIdLow = 0; // ID LSB: (0-0xFFFF) ([ExtId12-0][IDE][RTR] 0) (0="don't care") - sFilterConfig.FilterMaskIdHigh = 0b1000000000000000; // Mask MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) + sFilterConfig.FilterMaskIdHigh = 0b1000000000000000; // Mask MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) sFilterConfig.FilterMaskIdLow = 0; // Mask LSB: (0-0xFFFF) ([ExtId12-0][IDE][RTR] 0) (0="don't care") sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; // Store message in FIFO1 (CAN_FILTER_FIFO0 / CAN_FILTER_FIFO1) sFilterConfig.FilterActivation = CAN_FILTER_ENABLE; // CAN_FILTER_ENABLE / CAN_FILTER_DISABLE @@ -634,7 +642,7 @@ HAL_StatusTypeDef CAN1_Start(void) sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // CAN_FILTERSCALE_16BIT / CAN_FILTERSCALE_32BIT (See Figure 342 in RM0090) sFilterConfig.FilterIdHigh = 0b0000; // ID MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) sFilterConfig.FilterIdLow = 0x0000; // ID LSB: (0-0xFFFF) ([ExtId12-0][IDE][RTR] 0) (0="don't care") - sFilterConfig.FilterMaskIdHigh = 0b0000; // Mask MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) + sFilterConfig.FilterMaskIdHigh = 0b0000; // Mask MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) sFilterConfig.FilterMaskIdLow = 0x0000; // Mask LSB: (0-0xFFFF) ([ExtId12-0][IDE][RTR] 0) (0="don't care") sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO1; // Store message in FIFO0 (CAN_FILTER_FIFO0 / CAN_FILTER_FIFO1) sFilterConfig.FilterActivation = CAN_FILTER_ENABLE; // CAN_FILTER_ENABLE / CAN_FILTER_DISABLE @@ -644,10 +652,10 @@ HAL_StatusTypeDef CAN1_Start(void) // Activate RX FIFO0/FIFO1 new message interrupt HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING); // Calls CAN1_RX0_IRQHandler -> HAL_CAN_IRQHandler -> HAL_CAN_RxFifo0MsgPendingCallback HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO1_MSG_PENDING); // Calls CAN1_RX1_IRQHandler -> HAL_CAN_IRQHandler -> HAL_CAN_RxFifo1MsgPendingCallback - + // Activate RX FIFO0/FIFO1 overrun interrupt HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_OVERRUN); // Calls CAN1_RX0_IRQHandler -> HAL_CAN_ErrorCallback - HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO1_OVERRUN); // Calls CAN1_RX1_IRQHandler -> HAL_CAN_ErrorCallback + HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO1_OVERRUN); // Calls CAN1_RX1_IRQHandler -> HAL_CAN_ErrorCallback HAL_CAN_ActivateNotification(&hcan1, CAN_IT_ERROR); // Calls CAN1_RX0_IRQHandler -> HAL_CAN_ErrorCallback @@ -658,39 +666,31 @@ HAL_StatusTypeDef CAN1_Start(void) return CAN_Send_Gcode_2params('M', 997, 0, 0, 0, 0); // M997, reset head at host startup } -void HAL_CAN_RxFifoMsgPending(CAN_HandleTypeDef *hcan, uint32_t RxFifo) // ISR! New FIFO 0/1 message interrupt handler -{ +void HAL_CAN_RxFifoMsgPending(CAN_HandleTypeDef *hcan, uint32_t RxFifo) { // ISR! New FIFO 0/1 message interrupt handler CAN_RxHeaderTypeDef RxHeader; // RX Header buffer for FIFO0 uint8_t CAN_RX_buffer_Fifo[8]; // CAN MESSAGE DATA BUFFER - if (HAL_CAN_GetRxMessage(&hcan1, RxFifo, &RxHeader, CAN_RX_buffer_Fifo) == HAL_OK) // Get message from CAN_RX_FIFO0 - { - if ((RxHeader.StdId & CAN_IO_MASK) != CAN_io_state) // First handle time critical IO update - { + if (HAL_CAN_GetRxMessage(&hcan1, RxFifo, &RxHeader, CAN_RX_buffer_Fifo) == HAL_OK) { // Get message from CAN_RX_FIFO0 + if ((RxHeader.StdId & CAN_IO_MASK) != CAN_io_state) { // First handle time critical IO update CAN_io_state = (RxHeader.StdId & CAN_IO_MASK); endstops.update(); } - if (RxHeader.StdId & CAN_STRING_MESSAGE_MASK) // Head sends a string message - { + if (RxHeader.StdId & CAN_STRING_MESSAGE_MASK) { // Head sends a string message char * CAN_RX_p = (char *)CAN_RX_buffer_Fifo; - for (uint32_t i = 0; i < RxHeader.DLC; i++) - { + for (uint32_t i = 0; i < RxHeader.DLC; i++) { string_message[string_message_index++ % 128] = CAN_RX_p[i]; // Copy message to global buffer - - if (CAN_RX_p[i] == '\n') - { + + if (CAN_RX_p[i] == '\n') { string_message_complete = true; // Print buffer string_message[string_message_index % 128] = 0; // Close string with \0 } } } - else - if (RxHeader.DLC == 4) // FIFO0, head sent a temperature update (DLC = Data Length Code == 4 bytes) - { + else if (RxHeader.DLC == 4) { // FIFO0, head sent a temperature update (DLC = Data Length Code == 4 bytes) float * fp = (float *)CAN_RX_buffer_Fifo; // FIFO0 thermalManager.temp_hotend[0].celsius = *fp; // Set E0 hotend temperature - Last_CAN_Temp_Report = millis(); + Next_CAN_Temp_Report = millis() + 10000; FirstE0Error = true; // Reset error status } @@ -699,18 +699,15 @@ void HAL_CAN_RxFifoMsgPending(CAN_HandleTypeDef *hcan, uint32_t RxFifo) // ISR! } } -void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) // ISR! New FIFO0 message interrupt handler -{ - HAL_CAN_RxFifoMsgPending(hcan, CAN_RX_FIFO0); +void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { // ISR! New FIFO0 message interrupt handler + HAL_CAN_RxFifoMsgPending(hcan, CAN_RX_FIFO0); } -void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan) // ISR! New FIFO1 message interrupt handler -{ - HAL_CAN_RxFifoMsgPending(hcan, CAN_RX_FIFO1); +void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan) { // ISR! New FIFO1 message interrupt handler + HAL_CAN_RxFifoMsgPending(hcan, CAN_RX_FIFO1); } -void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) // Interrupt handler for any CAN error -{ +void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) { // Interrupt handler for any CAN error HAL_CAN_error_code = hcan->ErrorCode; // Store the received error code } @@ -749,4 +746,4 @@ CAN WEAK CALLBACKS WHEN USING STANDARD WEAK CAN1_RX0_IRQHandler------------> INT __weak void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) // CAN_IT_ERROR */ -#endif // CAN_MASTER \ No newline at end of file +#endif // CAN_MASTER diff --git a/Marlin/src/HAL/STM32/CAN.h b/Marlin/src/HAL/STM32/CAN.h deleted file mode 100644 index 32dc76a2ec21..000000000000 --- a/Marlin/src/HAL/STM32/CAN.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include "../../inc/MarlinConfig.h" - -#ifdef CAN_MASTER - - extern uint32_t CAN_io_state; // CAN virtual IO variable - - #define CAN_IO_MASK 0b11111 // Masks for the 5 virtual IO bits - #define CAN_PROBE_MASK 1 // Virtual IO bit - #define CAN_FILAMENT_MASK 2 // Virtual IO bit - #define CAN_X_ENDSTOP_MASK 4 // Virtual IO bit - #define CAN_Y_ENDSTOP_MASK 8 // Virtual IO bit - #define CAN_Z_ENDSTOP_MAS 16 // Virtual IO bit - #define CAN_STRING_MESSAGE_MASK 32 // Signals the head sends a string message - #define CAN_REQUEST_SETUP_MASK 64 // Signals the head requests setup information - #define CAN_TMC_OT_MASK 128 // Signals the head signals a TMC Over Temp error - #define CAN_E0_TARGET_MASK 256 // Signals E0 or E1 - #define CAN_ERROR_MASK 512 // Signals the head encountered an error - - HAL_StatusTypeDef CAN1_Start(void); // FUNCTION PROTOTYPES - HAL_StatusTypeDef CAN1_Stop(void); - HAL_StatusTypeDef CAN_Send_Gcode(void); - HAL_StatusTypeDef CAN_Send_Gcode_2params(uint32_t Gcode_type, uint32_t Gcode_no, uint32_t parameter1, float value1, uint32_t parameter2, float value2); - void CAN_Send_Setup(); // Send host configuration to head - void CAN_Idle(); // Idle CAN task - -#endif // CAN_MASTER \ No newline at end of file diff --git a/Marlin/src/HAL/STM32/Servo.cpp b/Marlin/src/HAL/STM32/Servo.cpp index dc6888ff9d11..5ef3e62f1c6a 100644 --- a/Marlin/src/HAL/STM32/Servo.cpp +++ b/Marlin/src/HAL/STM32/Servo.cpp @@ -29,6 +29,10 @@ #include "Servo.h" +#if ENABLED(CAN_MASTER) + #include "../shared/CAN.h" +#endif + static uint_fast8_t servoCount = 0; static libServo *servos[NUM_SERVOS] = {0}; constexpr millis_t servoDelay[] = SERVO_DELAY; @@ -72,19 +76,16 @@ int8_t libServo::attach(const int pin, const int min, const int max) { void libServo::move(const int value) { -#ifdef CAN_MASTER // IRON, FORWARD UNDERWATER SERVO COMMAND TO HEAD - int angles[2] = Z_SERVO_ANGLES; - - // Translate M280 S10 to M401, M280 S90 to M402 - HAL_StatusTypeDef CAN_Send_Gcode_2params(uint32_t Gcode_type, uint32_t Gcode_no, uint32_t parameter1, float value1, uint32_t parameter2, float value2); // PROTOTYPE - if (value == angles[0]) // Deploy angle - CAN_Send_Gcode_2params('M', 401, 0, 0, 0, 0); // IRON, send "M401" instead, enables interrupt etc. - else - if (value == angles[1]) // Stow angle - CAN_Send_Gcode_2params('M', 402, 0, 0, 0, 0); // IRON, send "M401" instead, enables interrupt etc. - else - CAN_Send_Gcode_2params('M', 280, 'S', value, 'P', 0); // M280 S[value] P0 -#endif // IRON + #if ENABLED(CAN_MASTER) // 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_Send_Gcode_2params('M', 401, 0, 0, 0, 0); // Deploy Angle: Send "M401" instead, enables interrupt etc. + else if (value == angles[1]) + CAN_Send_Gcode_2params('M', 402, 0, 0, 0, 0); // Stow Angle: Send "M402" instead, enables interrupt etc. + else + CAN_Send_Gcode_2params('M', 280, 'S', value, 'P', 0); // M280 S[value] P0 + #endif if (attach(0) >= 0) { stm32_servo.write(value); diff --git a/Marlin/src/HAL/shared/CAN.h b/Marlin/src/HAL/shared/CAN.h new file mode 100644 index 000000000000..c8e410241293 --- /dev/null +++ b/Marlin/src/HAL/shared/CAN.h @@ -0,0 +1,50 @@ +/** + * 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" + +//#define CAN_DEBUG // Define to show gcodes send to HEAD + +#define SOUND_OK 880 +#define SOUND_ERROR 40 + +extern uint32_t CAN_io_state; // CAN virtual IO variable + +#define CAN_IO_MASK 0b11111 // Masks for the 5 virtual IO bits +#define CAN_PROBE_MASK 1 // Virtual IO bit +#define CAN_FILAMENT_MASK 2 // Virtual IO bit +#define CAN_X_ENDSTOP_MASK 4 // Virtual IO bit +#define CAN_Y_ENDSTOP_MASK 8 // Virtual IO bit +#define CAN_Z_ENDSTOP_MAS 16 // Virtual IO bit +#define CAN_STRING_MESSAGE_MASK 32 // Signals the head sends a string message +#define CAN_REQUEST_SETUP_MASK 64 // Signals the head requests setup information +#define CAN_TMC_OT_MASK 128 // Signals the head signals a TMC Over Temp error +#define CAN_E0_TARGET_MASK 256 // Signals E0 or E1 +#define CAN_ERROR_MASK 512 // Signals the head encountered an error + +HAL_StatusTypeDef CAN1_Start(); // FUNCTION PROTOTYPES +HAL_StatusTypeDef CAN1_Stop(); +HAL_StatusTypeDef CAN_Send_Gcode(); +HAL_StatusTypeDef CAN_Send_Gcode_2params(uint32_t Gcode_type, uint32_t Gcode_no, uint32_t parameter1, float value1, uint32_t parameter2, float value2); +void CAN_Send_Setup(); // Send host configuration to head +void CAN_Idle(); // Idle CAN task diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index 3b73fd8b5014..d04600383323 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -34,8 +34,8 @@ #include "HAL/shared/esp_wifi.h" #include "HAL/shared/cpu_exception/exception_hook.h" -#ifdef CAN_MASTER // IRON, ADDED - #include HAL_PATH(., CAN.h) +#if ENABLED(CAN_MASTER) + #include "HAL/shared/CAN.h" #endif #if ENABLED(WIFISUPPORT) @@ -1194,14 +1194,15 @@ void setup() { while (!MYSERIAL3.connected() && PENDING(millis(), serial_connect_timeout)) { /*nada*/ } #endif #endif - SERIAL_ECHOLNPGM("\nstart\n"); // IRON, ADDED \n -#ifdef CAN_MASTER // IRON, REPORT CAN START STATUS - if (CAN1_Start() == HAL_OK) - SERIAL_ECHOLNPGM(">>> CAN1 Start: OK"); - else - SERIAL_ECHOLNPGM(">>> CAN1 Start: FAILED!"); -#endif + SERIAL_ECHOLNPGM(TERN(CAN_MASTER, "\nstart\n", "start")); + + #if ENABLED(CAN_MASTER) + SERIAL_ECHOLN( + F(">>> CAN1 Start: "), + CAN1_Start() == HAL_OK ? F("OK") : F("FAILED!") + ); + #endif // Set up these pins early to prevent suicide #if HAS_KILL diff --git a/Marlin/src/feature/pause.cpp b/Marlin/src/feature/pause.cpp index 3af6b59709e4..3589847761c5 100644 --- a/Marlin/src/feature/pause.cpp +++ b/Marlin/src/feature/pause.cpp @@ -225,13 +225,8 @@ bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load switch (active_extruder) { REPEAT_1(NUM_RUNOUT_SENSORS, _CASE_INSERTED) } - #else - #ifdef CAN_MASTER // IRON, USE VIRTUAL CAN FILAMENT RUNOUT STATUS - if ((!!(CAN_io_state & CAN_FILAMENT_MASK)) != FIL_RUNOUT_STATE) - wait_for_user = false; #else if (!FILAMENT_IS_OUT()) wait_for_user = false; - #endif // IRON #endif #endif idle_no_sleep(); diff --git a/Marlin/src/feature/runout.h b/Marlin/src/feature/runout.h index 8ecb94a21b7f..8cbad8e61eb2 100644 --- a/Marlin/src/feature/runout.h +++ b/Marlin/src/feature/runout.h @@ -33,8 +33,8 @@ #include "pause.h" // for did_pause_print #include "../MarlinCore.h" // for printingIsActive() -#ifdef CAN_MASTER // IRON, ADDED - #include HAL_PATH(.., CAN.h) +#if ENABLED(CAN_MASTER) + #include "../HAL/shared/CAN.h" #endif #include "../inc/MarlinConfig.h" @@ -57,6 +57,12 @@ #define FILAMENT_IS_OUT() (READ(FIL_RUNOUT_PIN) == FIL_RUNOUT_STATE) +#if ENABLED(CAN_MASTER) + #define RUNOUT_STATE(N) bool(CAN_io_state & CAN_FILAMENT_MASK) // CAN Virtual Filament Runout pin +#else + #define RUNOUT_STATE(N) READ(FIL_RUNOUT##N##_PIN) // DIO Filament Runout pin +#endif + typedef Flags< #if NUM_MOTION_SENSORS > NUM_RUNOUT_SENSORS NUM_MOTION_SENSORS @@ -211,13 +217,7 @@ class FilamentSensorBase { // Return a bitmask of runout pin states static uint8_t poll_runout_pins() { - -#ifdef CAN_MASTER // IRON, VIRTUAL FILAMENT RUNOUT PIN - #define _OR_RUNOUT(N) | ((CAN_io_state & CAN_FILAMENT_MASK) ? _BV((N) - 1) : 0) -#else - #define _OR_RUNOUT(N) | (READ(FIL_RUNOUT##N##_PIN) ? _BV((N) - 1) : 0) -#endif // IRON - + #define _OR_RUNOUT(N) | (RUNOUT_STATE(N) ? _BV((N) - 1) : 0) return (0 REPEAT_1(NUM_RUNOUT_SENSORS, _OR_RUNOUT)); #undef _OR_RUNOUT } diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp index 53d84ac27cb4..3744a384886b 100644 --- a/Marlin/src/gcode/gcode.cpp +++ b/Marlin/src/gcode/gcode.cpp @@ -28,10 +28,6 @@ #include "gcode.h" GcodeSuite gcode; -#ifdef CAN_MASTER // IRON - #include HAL_PATH(.., CAN.h) -#endif - #if ENABLED(WIFI_CUSTOM_COMMAND) extern bool wifi_custom_command(char * const command_ptr); #endif @@ -73,6 +69,11 @@ GcodeSuite gcode; #include "../feature/fancheck.h" #endif +#if ENABLED(CAN_MASTER) + #include "../HAL/shared/CAN.h" + #include "../libs/buzzer.h" +#endif + #include "../MarlinCore.h" // for idle, kill // Inactivity shutdown @@ -327,16 +328,14 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) { KEEPALIVE_STATE(IN_HANDLER); -#ifdef CAN_MASTER - if (CAN_Send_Gcode() != HAL_OK) // IRON, SEND COMMAND TO HEAD - { - SERIAL_ECHOPGM("Error: FAILED TO SEND GCODE CAN COMMAND TO HEAD: "); - SERIAL_ECHOLN_P(parser.command_ptr); - #ifndef IRON_DEBUG - BUZZ(1, SOUND_ERROR); + #if ENABLED(CAN_MASTER) + if (CAN_Send_Gcode() != HAL_OK) { // Send command to head + SERIAL_ECHOLN(F("Error: CAN failed to send \""), parser.command_ptr, '"'); + #ifndef CAN_DEBUG + BUZZ(1, SOUND_ERROR); + #endif + } #endif - } -#endif // IRON /** * Block all Gcodes except M511 Unlock Printer, if printer is locked diff --git a/Marlin/src/gcode/temp/M306.cpp b/Marlin/src/gcode/temp/M306.cpp index 57ed2518ff9e..4b4b69c287dc 100644 --- a/Marlin/src/gcode/temp/M306.cpp +++ b/Marlin/src/gcode/temp/M306.cpp @@ -27,7 +27,6 @@ #include "../gcode.h" #include "../../lcd/marlinui.h" #include "../../module/temperature.h" -#include "../../libs/numtostr.h" // IRON, ADDED /** * M306: MPC settings and autotune @@ -59,23 +58,30 @@ void GcodeSuite::M306() { #if ENABLED(MPC_AUTOTUNE) if (parser.seen_test('T')) { -#ifdef CAN_MASTER // IRON, MPC AUTOTUNE INFO - SERIAL_ECHOLNPGM(">>> Forwarding M306 to head board"); - SERIAL_ECHOLNPGM(">>> Store MPC setup in the host Configuration.h or use M500"); - SERIAL_ECHOLNPGM(">>> MPC heater power is: ", ftostr31ns(MPC_HEATER_POWER), " Watt"); - SERIAL_ECHOLNPGM(">>> 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(); -#endif // IRON + #if ENABLED(CAN_MASTER) // MPC Autotune info + + SERIAL_ECHOLNPGM( + ">>> Forwarding M306 to head board\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(); + + #endif + return; } #endif @@ -101,11 +107,10 @@ void GcodeSuite::M306_report(const bool forReplay/*=true*/) { report_heading(forReplay, F("Model predictive control")); -#ifdef CAN_MASTER // IRON, MPC AUTOTUNE INFO - if (forReplay) - SERIAL_ECHOLNPGM(">>> HOST M306 MPC SETTINGS:"); -#endif - + #if ENABLED(CAN_MASTER) // 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; diff --git a/Marlin/src/module/endstops.cpp b/Marlin/src/module/endstops.cpp index 5ce8f43b3551..dcb10c014edd 100644 --- a/Marlin/src/module/endstops.cpp +++ b/Marlin/src/module/endstops.cpp @@ -58,6 +58,10 @@ #include "probe.h" #endif +#if HAS_FILAMENT_SENSOR && !MULTI_FILAMENT_SENSOR + #include "../feature/runout.h" +#endif + #define DEBUG_OUT ALL(USE_SENSORLESS, DEBUG_LEVELING_FEATURE) #include "../core/debug_out.h" @@ -77,14 +81,14 @@ Endstops::endstop_mask_t Endstops::live_state = 0; #else #define READ_ENDSTOP(P) READ(P) #endif -#else - -#ifdef CAN_MASTER // IRON, READ VIRTUAL CAN IO PROBE STATUS IF NEEDED - #define READ_ENDSTOP(P) ((P == Z_MIN_PIN) ? ((CAN_io_state & CAN_PROBE_MASK)) : READ(P)) +#elif ENABLED(CAN_MASTER) // Read virtual CAN IO Probe status if needed + #if HAS_BED_PROBE + #define READ_ENDSTOP(P) ((P == Z_MIN_PIN) ? PROBE_READ() : READ(P)) + #else + #define READ_ENDSTOP(P) READ(P) + #endif #else #define READ_ENDSTOP(P) READ(P) -#endif // IRON, ADDED - #endif #if ENDSTOP_NOISE_THRESHOLD @@ -531,13 +535,10 @@ void __O2 Endstops::report_states() { print_es_state(extDigitalRead(pin) != state); } #undef _CASE_RUNOUT + #elif HAS_FILAMENT_SENSOR - #ifdef CAN_MASTER // IRON - print_es_state((!!(CAN_io_state & CAN_FILAMENT_MASK)) != FIL_RUNOUT1_STATE, F(STR_FILAMENT)); - #else - print_es_state(READ(FIL_RUNOUT1_PIN) != FIL_RUNOUT1_STATE, F(STR_FILAMENT)); - #endif // IRON + print_es_state(RUNOUT_STATE(1) != FIL_RUNOUT1_STATE, F(STR_FILAMENT)); #endif diff --git a/Marlin/src/module/probe.h b/Marlin/src/module/probe.h index e849a28324f5..41d59bb91afb 100644 --- a/Marlin/src/module/probe.h +++ b/Marlin/src/module/probe.h @@ -29,8 +29,8 @@ #include "motion.h" -#ifdef CAN_MASTER // IRON - #include HAL_PATH(.., CAN.h) +#if ENABLED(CAN_MASTER) + #include "../HAL/shared/CAN.h" #endif #if ENABLED(BLTOUCH) @@ -51,23 +51,13 @@ #if ENABLED(BD_SENSOR) #define PROBE_READ() bdp_state +#elif ENABLED(CAN_MASTER) + #define PROBE_READ() bool(CAN_io_state & CAN_PROBE_MASK) #elif USE_Z_MIN_PROBE - - #ifdef CAN_MASTER - #define PROBE_READ() ((CAN_io_state & CAN_PROBE_MASK)) // IRON, READ VIRTUAL IO - #else - #define PROBE_READ() READ(Z_MIN_PROBE_PIN) - #endif // IRON - + #define PROBE_READ() READ(Z_MIN_PROBE_PIN) #else - - #ifdef CAN_MASTER // IRON, VIRTUAL IO - #define PROBE_READ() ((CAN_io_state & CAN_PROBE_BIT)) // IRON, READ VIRTUAL IO - #else - #define PROBE_READ() READ(Z_MIN_PIN) - #endif - -#endif // IRON + #define PROBE_READ() READ(Z_MIN_PIN) +#endif #if USE_Z_MIN_PROBE #define PROBE_HIT_STATE Z_MIN_PROBE_ENDSTOP_HIT_STATE diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp index 1f78f4f9ffb5..fa984176d8a5 100644 --- a/Marlin/src/module/temperature.cpp +++ b/Marlin/src/module/temperature.cpp @@ -50,6 +50,10 @@ #include "motion.h" #endif +#if ENABLED(CAN_MASTER) + #include "../HAL/shared/CAN.h" +#endif + #if ENABLED(DWIN_CREALITY_LCD) #include "../lcd/e3v2/creality/dwin.h" #elif ENABLED(SOVOL_SV06_RTS) @@ -2734,11 +2738,9 @@ void Temperature::updateTemperaturesFromRawValues() { temp_bed.setraw(read_max_tc_bed()); #endif -#ifndef CAN_MASTER // IRON, NOT FOR MASTER, DON'T READ TEMPERATURE FROM SENSOR, GET IT VIA CAN BUS FROM HEAD - #if HAS_HOTEND + #if HAS_HOTEND && DISABLED(CAN_MASTER) // For CAN Master we'll get temperature from CAN bus HOTEND_LOOP() temp_hotend[e].celsius = analog_to_celsius_hotend(temp_hotend[e].getraw(), e); #endif -#endif // IRON TERN_(HAS_HEATED_BED, temp_bed.celsius = analog_to_celsius_bed(temp_bed.getraw())); TERN_(HAS_TEMP_CHAMBER, temp_chamber.celsius = analog_to_celsius_chamber(temp_chamber.getraw())); @@ -2751,9 +2753,7 @@ void Temperature::updateTemperaturesFromRawValues() { TERN_(FILAMENT_WIDTH_SENSOR, filwidth.update_measured_mm()); TERN_(HAS_POWER_MONITOR, power_monitor.capture_values()); - #if HAS_HOTEND - -#ifndef CAN_MASTER // IRON, ONLY FOR HEAD, NO TEMP SAMPLING ON MASTER + #if HAS_HOTEND && DISABLED(CAN_MASTER) // Only for Head, no temp sampling on Master #define _TEMPDIR(N) TEMP_SENSOR_IS_ANY_MAX_TC(N) ? 0 : TEMPDIR(N), static constexpr int8_t temp_dir[HOTENDS] = { REPEAT(HOTENDS, _TEMPDIR) }; @@ -2780,9 +2780,8 @@ void Temperature::updateTemperaturesFromRawValues() { TERN_(MULTI_MAX_CONSECUTIVE_LOW_TEMP_ERR, consecutive_low_temperature_error[e] = 0); } } -#endif // IRON, !CAN_MASTER - #endif // HAS_HOTEND + #endif // HAS_HOTEND && !CAN_MASTER #if ENABLED(THERMAL_PROTECTION_BED) if (TP_CMP(BED, temp_bed.getraw(), temp_sensor_range_bed.raw_max)) @@ -3382,16 +3381,17 @@ void Temperature::disable_all_heaters() { #if HAS_HOTEND -#ifdef CAN_MASTER // IRON, SHUTDOWN HOTEND IN HEAD TOO - CAN_Send_Gcode_2params('M', 104, 'S', 0, 0, 0); // IRON, M104 S0, switch off hotend heating - CAN_Send_Gcode_2params('M', 107, 0, 0, 0, 0); // IRON, M107, switch off part cooling fan - CAN_Send_Gcode_2params('M', 150, 'R', 255, 0, 0); // IRON, M150 R255, SET NEOPIXEL TO RED -#endif + #if ENABLED(CAN_MASTER) // Shut down the hotend in the head too + CAN_Send_Gcode_2params('M', 104, 'S', 0, 0, 0); // M104 S0 .... Switch off hotend heating + CAN_Send_Gcode_2params('M', 107, 0, 0, 0, 0); // M107 ....... Switch off part cooling fan + CAN_Send_Gcode_2params('M', 150, 'R', 255, 0, 0); // M150 R255 .. Set NeoPixel to red + #endif HOTEND_LOOP() { setTargetHotend(0, e); temp_hotend[e].soft_pwm_amount = 0; } + #endif #if HAS_TEMP_HOTEND diff --git a/Marlin/src/pins/pins.h b/Marlin/src/pins/pins.h index eb26103380c1..f9b0ee25235a 100644 --- a/Marlin/src/pins/pins.h +++ b/Marlin/src/pins/pins.h @@ -800,7 +800,7 @@ #elif MB(MKS_MONSTER8_V1) #include "stm32f4/pins_MKS_MONSTER8_V1.h" // STM32F4 env:mks_monster8 env:mks_monster8_usb_flash_drive env:mks_monster8_usb_flash_drive_msc #elif MB(MKS_MONSTER8_V2) - #include "stm32f4/pins_MKS_MONSTER8_V2.h" // STM32F4 env:mks_monster8 env:mks_monster8_usb_flash_drive env:mks_monster8_usb_flash_drive_msc + #include "stm32f4/pins_MKS_MONSTER8_V2.h" // STM32F4 env:mks_monster8 env:mks_monster8_usb_flash_drive env:mks_monster8_usb_flash_drive_msc env:mks_monster8_usb_flash_drive_msc_CAN #elif MB(ANET_ET4) #include "stm32f4/pins_ANET_ET4.h" // STM32F4 env:Anet_ET4_no_bootloader env:Anet_ET4_OpenBLT #elif MB(ANET_ET4P) diff --git a/Marlin/src/pins/stm32g0/pins_BTT_EBB42_V1_1.h b/Marlin/src/pins/stm32g0/pins_BTT_EBB42_V1_1.h index 2d6cdb73f43c..fef28f3f3682 100644 --- a/Marlin/src/pins/stm32g0/pins_BTT_EBB42_V1_1.h +++ b/Marlin/src/pins/stm32g0/pins_BTT_EBB42_V1_1.h @@ -27,7 +27,7 @@ * This board definition is to facilitate support for a Filament Extrusion * devices, used to convert waste plastic into 3D printable filament. * This board is NOT a general 3D printing controller; it is NOT supported - * as a toolboard via CANBUS (as it was originally designed) or any device + * as a toolboard via CAN bus (as it was originally designed) or any device * that requires kinematics. */ diff --git a/ini/stm32f4.ini b/ini/stm32f4.ini index b90b46ae7375..fbe68125092c 100644 --- a/ini/stm32f4.ini +++ b/ini/stm32f4.ini @@ -619,6 +619,7 @@ build_flags = ${stm_flash_drive.build_flags} ${stm32f4_I2C1_CAN.build_flag extends = env:mks_monster8_usb_flash_drive build_flags = ${env:mks_monster8_usb_flash_drive.build_flags} -DUSBD_USE_CDC_MSC + [env:mks_monster8_usb_flash_drive_msc_CAN] extends = env:mks_monster8_usb_flash_drive build_flags = ${env:mks_monster8_usb_flash_drive.build_flags} diff --git a/platformio.ini b/platformio.ini index 88e468c49e28..ed1670dc6daf 100644 --- a/platformio.ini +++ b/platformio.ini @@ -13,7 +13,7 @@ [platformio] src_dir = Marlin boards_dir = buildroot/share/PlatformIO/boards -default_envs = #define MOTHERBOARD BOARD_MKS_MONSTER8_V2 +default_envs = mega2560 include_dir = Marlin extra_configs = Marlin/config.ini From 5b97aefb5b7d636a89288749cb228be13352f99e Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Sat, 30 Nov 2024 05:54:26 +0800 Subject: [PATCH 04/21] Added FDCAN tool head part Added FDCAN tool head part Added NTP style CAN bus time sync (test) --- Marlin/Configuration.h | 1 + Marlin/src/HAL/STM32/CAN.cpp | 83 ++- Marlin/src/HAL/STM32/FDCAN.cpp | 588 ++++++++++++++++++ Marlin/src/HAL/shared/CAN.h | 22 +- Marlin/src/HAL/shared/FDCAN.h | 48 ++ Marlin/src/MarlinCore.cpp | 24 +- Marlin/src/gcode/temp/M306.cpp | 21 + Marlin/src/module/endstops.cpp | 6 + Marlin/src/module/temperature.cpp | 7 + Marlin/src/pins/pins.h | 4 +- .../pins/stm32f4/pins_MKS_MONSTER8_common.h | 11 +- Marlin/src/pins/stm32g0/pins_BTT_EBB42_V1_1.h | 3 + ini/stm32g0.ini | 16 + 13 files changed, 792 insertions(+), 42 deletions(-) create mode 100644 Marlin/src/HAL/STM32/FDCAN.cpp create mode 100644 Marlin/src/HAL/shared/FDCAN.h diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index 2cdffe5179ac..e315634fbb30 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -126,6 +126,7 @@ // Enable CAN bus support and protocol //#define CAN_MASTER +//#define CAN_TOOLHEAD // Enable the Bluetooth serial interface on AT90USB devices //#define BLUETOOTH diff --git a/Marlin/src/HAL/STM32/CAN.cpp b/Marlin/src/HAL/STM32/CAN.cpp index 89292f986d3f..27c3bb85d7ae 100644 --- a/Marlin/src/HAL/STM32/CAN.cpp +++ b/Marlin/src/HAL/STM32/CAN.cpp @@ -64,7 +64,7 @@ #define CAN_STRING_MESSAGE_MASK 32 // Signals the head sent a string message #define CAN_REQUEST_SETUP_MASK 64 // Signals the head requests setup information #define CAN_TMC_OT_MASK 128 // Signals the head signals a TMC Over Temp error -#define CAN_E0_TARGET_MASK 256 // Signals E0 or E1 + #define CAN_REQUEST_TIME_SYNC_MASK 256 // Signals E0 or E1 #define CAN_ERROR_MASK 512 // Signals the head encountered an error #define PARAMETER1_OFFSET 0 @@ -78,6 +78,8 @@ #define GCODE_TYPE_M 2 #define GCODE_TYPE_T 3 +#define GCODE_TIME_SYNC_NO 7777 // Just a unique unused number M7777 + extern "C" void CAN1_RX0_IRQHandler(); // Override weak CAN FIFO0 interrupt handler extern "C" void CAN1_RX1_IRQHandler(); // Override weak CAN FIFO1 interrupt handler extern "C" void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan); // Override weak CAN interrupt callback for FIFO0 @@ -88,7 +90,9 @@ CAN_HandleTypeDef hcan1 = { 0 }; // The CAN1 handle CAN_TxHeaderTypeDef TxHeader = { 0 }; // Header to send a CAN message volatile uint32_t CAN_io_state = 0; // Virtual IO state variable volatile bool CAN_head_error = 0; // Register if an error was reported by the head -volatile bool CAN_head_setup_request = false; // Signals the head requesting setup information +volatile bool CAN_head_setup_request = false; // Signals the head requested setup information +volatile bool CAN_time_sync_request = false; // Signals the head requested a time sync +volatile uint32_t time_sync_request_time = 0; // Record the time the time sync request was received volatile uint32_t gcode_counter = 0; // Count amount of gcodes received volatile uint32_t HAL_CAN_error_code = 0; // Record a host CAN error message @@ -111,6 +115,37 @@ void CAN1_RX1_IRQHandler() { // CAN FIFO1 Interrupt handler overrides standard w //HAL_CAN_RxFifo1MsgPendingCallback(&hcan1); // Call the required callback directly, faster but no error reporting } +// Send sync timestamp +HAL_StatusTypeDef CAN_Send_Timestamp() // Request receive timestamp + request response timestamp +{ + HAL_StatusTypeDef status = HAL_OK; + uint32_t TxMailbox; // Stores which Mailbox (0-2) is used to store the Sent TX message + + TxHeader.IDE = CAN_EXTENDED_ID; + TxHeader.DLC = 8; // Send sync time t1(receive time, uint32_t) and t2(response time, uint32_t) + TxHeader.StdId = (TxHeader.StdId ^ STDID_FIFO_BIT); // Toggle FIFO bit 10, keep FIFO toggling in sync + TxHeader.ExtId = ((TxHeader.ExtId ^ EXTID_FIFO_BIT) & EXTID_FIFO_BIT) + // Toggle FIFO bit 28 + ((TxHeader.DLC >> 2) << PARAMETER_COUNT_OFFSET) + // Data bytes (4 or 8) + (GCODE_TYPE_M << GCODE_TYPE_OFFSET) + // G/M/T/D-code + ((GCODE_TIME_SYNC_NO & GCODE_NUMBER_MASK) << GCODE_NUMBER_OFFSET) + // Gcode number + ((2 & PARAMETER_MASK) << PARAMETER2_OFFSET) + // Second parameter + ((1 & PARAMETER_MASK) << PARAMETER1_OFFSET); // First parameter + uint8_t CAN_tx_buffer[8]; // 8 bytes CAN data TX buffer + uint32_t * uint32p = (uint32_t *)CAN_tx_buffer; // Point to TX buffer + *uint32p++ = time_sync_request_time; + + uint32_t ms = millis(); // Wait until the FIFO is empty + while ((HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) < 3) && (millis() - ms < 25)) { } // BLOCKING! Wait max 25ms! + + *uint32p = micros(); // Only record the response time at the last possible moment + status = HAL_CAN_AddTxMessage(&hcan1, &TxHeader, CAN_tx_buffer, &TxMailbox); // Send message + + if (status == HAL_OK) // Count sent gcode messages + gcode_counter++; + + return status; +} + // Send specified Gcode with max 2 parameters and 2 values via CAN bus HAL_StatusTypeDef CAN_Send_Gcode_2params(uint32_t Gcode_type, uint32_t Gcode_no, uint32_t parameter1, float value1, uint32_t parameter2, float value2) { switch (Gcode_type) { @@ -169,12 +204,12 @@ HAL_StatusTypeDef CAN_Send_Gcode_2params(uint32_t Gcode_type, uint32_t Gcode_no, ((TxHeader.DLC >> 2) << PARAMETER_COUNT_OFFSET) + // Data bytes (4 or 8) (Gcode_type << GCODE_TYPE_OFFSET) + // G/M/T/D-code ((Gcode_no & GCODE_NUMBER_MASK) << GCODE_NUMBER_OFFSET) + // Gcode number - ((parameter2 & PARAMETER_MASK) << PARAMETER2_OFFSET) + // First parameter - ((parameter1 & PARAMETER_MASK) << PARAMETER1_OFFSET); // Second parameter + ((parameter2 & PARAMETER_MASK) << PARAMETER2_OFFSET) + // Second parameter + ((parameter1 & PARAMETER_MASK) << PARAMETER1_OFFSET); // First parameter uint8_t CAN_tx_buffer[8]; // 8 bytes CAN data TX buffer float * fp = (float *)CAN_tx_buffer; // Point to TX buffer *fp++ = value1; - *fp-- = value2; + *fp = value2; const uint32_t ms = millis(); // Don't send too fast! while ((HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) == 0) && (millis() - ms < 25)) { } // BLOCKING! Wait max 25ms! @@ -272,27 +307,20 @@ HAL_StatusTypeDef CAN_Send_Setup() { // Send setup to HEAD CAN_Send_Gcode_2params('M', 919, 'O', off, 'P' , Hysteresis End); CAN_Send_Gcode_2params('M', 919, 'S', Hysteresis Start, 0, 0); */ - #if USE_CONTROLLER_FAN - /* - CAN_Send_Gcode_2params('M', 710, 'E', 1, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P - CAN_Send_Gcode_2params('M', 710, 'E', 2, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P - CAN_Send_Gcode_2params('M', 710, 'E', 3, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P - CAN_Send_Gcode_2params('M', 710, 'E', 4, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P - CAN_Send_Gcode_2params('M', 710, 'E', 5, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P - CAN_Send_Gcode_2params('M', 710, 'E', 6, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P - CAN_Send_Gcode_2params('M', 710, 'E', 7, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P - CAN_Send_Gcode_2params('M', 710, 'E', 8, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P - CAN_Send_Gcode_2params('M', 710, 'E', 9, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P - CAN_Send_Gcode_2params('M', 710, 'E', 10, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P - CAN_Send_Gcode_2params('M', 710, 'E', 11, 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P - */ - // IRON, M710 SIGNALS TO THE HEAD THAT THE CAN CONFIGURATION IS COMPLETE, USE IT AS THE LAST GCODE TO SEND - return CAN_Send_Gcode_2params('M', 710, 'E', int(controllerFan.settings.extruder_auto_fan_speed), 'P', int(controllerFan.settings.probing_auto_fan_speed)); // M710 E P + #if ENABLED(USE_CONTROLLER_FAN) + // M710 SIGNALS TO THE HEAD THAT THE CAN CONFIGURATION IS COMPLETE, USE IT AS THE LAST GCODE TO SEND + return CAN_Send_Gcode_2params('M', 710, 'E', controllerFan.settings.extruder_auto_fan_speed, 0, 0); // M710 E #endif return HAL_OK; } -void CAN_Idle() { // Tasks that cannot be done in the ISR +void CAN_idle() { // Tasks that cannot be done in the ISR + if (CAN_time_sync_request) { + if (CAN_Send_Timestamp() == HAL_OK) { + CAN_time_sync_request = false; + } + } + if (CAN_head_setup_request) // The head requested the setup data CAN_Send_Setup(); @@ -493,8 +521,8 @@ HAL_StatusTypeDef CAN_Send_Gcode() { // Forward a Marlin Gcode via CAN (uses par values[parameter_counter++] = NAN; // Not A Number, indicates no parameter value is present } - if (parameter_counter == 8) { // Max 8 parameters - parameter_counter--; // Max is 7 parameters + if (parameter_counter == 8) { // Max is 7 parameters + parameter_counter--; SERIAL_ECHOLNPGM("\nError: TOO MANY PARAMETERS (> 7): ", parser.command_ptr); BUZZ(1, SOUND_ERROR); break; @@ -694,7 +722,12 @@ void HAL_CAN_RxFifoMsgPending(CAN_HandleTypeDef *hcan, uint32_t RxFifo) { // ISR FirstE0Error = true; // Reset error status } - CAN_head_setup_request = (RxHeader.StdId & CAN_REQUEST_SETUP_MASK) > 0; // FIFO0, head signals request for data + if (RxHeader.StdId & CAN_REQUEST_TIME_SYNC_MASK) {; // FIFO0, head signals request for time stamp + time_sync_request_time = micros(); // Record the time sync request receive time + CAN_time_sync_request = true; + } + + CAN_head_setup_request = (RxHeader.StdId & CAN_REQUEST_SETUP_MASK) > 0; // FIFO0, head signals request for setup data CAN_head_error = (RxHeader.StdId & CAN_ERROR_MASK) > 0; // FIFO0, head signals an error } } diff --git a/Marlin/src/HAL/STM32/FDCAN.cpp b/Marlin/src/HAL/STM32/FDCAN.cpp new file mode 100644 index 000000000000..a942acdbe7c7 --- /dev/null +++ b/Marlin/src/HAL/STM32/FDCAN.cpp @@ -0,0 +1,588 @@ +/** + * 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/MarlinConfigPre.h" + +#if ENABLED(CAN_TOOLHEAD) + +#include "../platforms.h" +#include "../../gcode/gcode.h" +#include "../../gcode/parser.h" +#include "../../gcode/queue.h" +#include "../../module/temperature.h" +#include "../../libs/numtostr.h" +#include "../../inc/MarlinConfig.h" +#include "../../feature/controllerfan.h" +#include "../../core/serial.h" + +#include "../SHARED/FDCAN.h" + +#define CAN_DEBUG // Enable to show CAN debug messages + +#define FDCAN_RX_FIFO0_MASK (FDCAN_IR_RF0L | FDCAN_IR_RF0F | FDCAN_IR_RF0N) // Fifo 0: Message lost | Fifo full | New message +#define FDCAN_RX_FIFO1_MASK (FDCAN_IR_RF1L | FDCAN_IR_RF1F | FDCAN_IR_RF1N) // Fifo 1: Message lost | Fifo full | New message +#define FDCAN_RX_FIFO_MASK (FDCAN_RX_FIFO0_MASK | FDCAN_RX_FIFO1_MASK) // Fifo : Message lost | Fifo full | New message +#define HAL_TIM_FLAG_ALL (TIM_FLAG_CC1 | TIM_FLAG_CC2 | TIM_FLAG_CC3 | TIM_FLAG_CC4 | TIM_FLAG_UPDATE | TIM_FLAG_BREAK | TIM_FLAG_BREAK2 | TIM_FLAG_TRIGGER | TIM_FLAG_COM) + +#define CAN_TIMESTAMP_NO 7777 + +// IMPORTANT NOTE +// =============== +// 1. In \Users\\.platformio\packages\framework-arduinoststm32@4.20600.231001\libraries\SrcWrapper\src\HardwareTimer.cpp +// Add function "__weak" in front of "void TIM16_IRQHandler(void)" + +FDCAN_HandleTypeDef hfdcan2; + +bool Head_Not_configured = true; // Check if the configuration was send to the head +char gcode_type[4] = { 'D', 'G', 'M', 'D' }; // This way an extended ID is always > 2048 +//char * codeP; // code can walk along with progress in can_gcode +//char can_gcode[MAX_CMD_SIZE + 20]; +char can_gcode_buffer[MAX_CMD_SIZE + 20]; // Gcode that is being received in several CAN messages +volatile uint32_t CAN_Error_Code = 0; // Signals an CAN error occured, report to host +volatile uint32_t Old_Error_Code = 0; // Remember last error code so messages don't repeat +volatile uint32_t gcode_counter = 0; // Count received gcode lines (debugging) +volatile uint32_t CAN_message_counter = 0; // Count sent CAN message (debugging) +volatile bool send_gcode_count = false; +volatile uint32_t t[5] = { 0, 0, 0, 0, 0 }; // Timestamps for time sync +volatile bool CAN_request_time_sync = false; // Request a timestamp +volatile uint32_t time_offset = 0; // Time offset in micro seconds in relation to host time + +extern "C" void TIM16_IRQHandler(void); +extern "C" void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs); +extern "C" void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs); + +void CAN_Add_Parameters(uint32_t parameter, float value, char *codeP) { // Add received CAN parameters to can_gcode string, used in ISR! + *codeP++ = char(parameter + 64); // Add parameter 'X' + + if (!isnan(value)) { // No value behind parameter if value is "Not A Number" + if (value < 0.0) { + *codeP++ = '-'; // Add minus sign to gcode string + value = -value; // Make value positive + } + + // Convert float to string + uint32_t i1 = int(value); // Get integer part of float + itoa(i1, codeP, 10); // Assume 1-4 digits + codeP += strlen(codeP); // Adjust pointer + value = (value - i1); // Get fractional part + + uint32_t j = 0; + uint32_t flag = 0; + for (int i = 0; i < 5; i++) { // 6 digits, 1 extra for rounding + value *= 10.0; + i1 = int(value); + j = (10 * j) + i1; + value = value - i1; + } + + if (value >= 0.5) + j++; // Round up + + if (j) { + *codeP++ = '.'; + if (j < 10000) { + *codeP++ = '0'; + if (j < 1000) { + *codeP++ = '0'; + if (j < 100) { + *codeP++ = '0'; + if (j < 10) + *codeP++ = '0'; + } + } + } + + while (!(j % 10)) // Remove trailing zeros, 120 --> 12 + j = j / 10; + + itoa(j, codeP, 10); + } + } +} + +HAL_StatusTypeDef CAN_Receive(uint32_t Fifo) { // ISR! Process received CAN message in interrupt handler! + HAL_StatusTypeDef status = HAL_OK; + static volatile uint32_t parameter_counter = 0; + char * codeP = can_gcode_buffer; // Put gcode pointer to start of can_gcode_buffer; + uint8_t can_rxbuf[8]; + + FDCAN_RxHeaderTypeDef CanRxHeader; // Receive CAN message buffer + + status = HAL_FDCAN_GetRxMessage(&hfdcan2, Fifo, &CanRxHeader, can_rxbuf); + if (status != HAL_OK) + return status; + + uint32_t identifier = CanRxHeader.Identifier; + if (CanRxHeader.IdType == FDCAN_EXTENDED_ID) { // Receiving new gcode + + if (((identifier >> IDENTIFIER_GCODE_NUMBER_OFFSET) & IDENTIFIER_GCODE_MASK) == ((GCODE_TYPE_M << (IDENTIFIER_GCODE_TYPE_OFFSET - IDENTIFIER_GCODE_NUMBER_OFFSET)) + CAN_TIMESTAMP_NO)) { // TIME SYNC RESPONSE MESSAGE + uint32_t *uint32p = (uint32_t *)can_rxbuf; + t[3] = micros(); // Record time sync response message receive time + t[1] = *uint32p++; + t[2] = *uint32p; + return status; + } + + // Check for M997 reset command + if (((identifier >> IDENTIFIER_GCODE_NUMBER_OFFSET) & IDENTIFIER_GCODE_MASK) == ((GCODE_TYPE_M << (IDENTIFIER_GCODE_TYPE_OFFSET - IDENTIFIER_GCODE_NUMBER_OFFSET)) + 997)) { // CODE IS M997, RESTART HEAD UNCONDITIONALLY + flashFirmware(0); + while (1); + } + + if (((identifier >> IDENTIFIER_GCODE_NUMBER_OFFSET) & IDENTIFIER_GCODE_MASK) == ((GCODE_TYPE_M << (IDENTIFIER_GCODE_TYPE_OFFSET - IDENTIFIER_GCODE_NUMBER_OFFSET)) + 115)) + CAN_request_time_sync = true; // M115 --> Request a time sync from the host + + memset(can_gcode_buffer, 0, MAX_CMD_SIZE); // Clear buffer + + if (parameter_counter) // Previous gcode must be complete, no more pending parameters + CAN_Error_Code |= CAN_ERROR_INCOMPLETE_GCODE_RECEIVED; + + parameter_counter = (identifier >> IDENTIFIER_PARAMETER_COUNT_OFFSET) & IDENTIFIER_PARAMETER_COUNT_MASK; // Get parameter_counter, bits 25, 26, 27 of 29 bit extended identifier + + *codeP++ = gcode_type[(identifier >> IDENTIFIER_GCODE_TYPE_OFFSET) & IDENTIFIER_GCODE_TYPE_MASK]; // "G" + itoa((identifier >> IDENTIFIER_GCODE_NUMBER_OFFSET) & IDENTIFIER_GCODE_NUMBER_MASK, codeP, 10); // G"92" + + // Check if head configuration was received, marked by M710 command + if (((identifier >> IDENTIFIER_GCODE_NUMBER_OFFSET) & IDENTIFIER_GCODE_MASK) == ((GCODE_TYPE_M << (IDENTIFIER_GCODE_TYPE_OFFSET - IDENTIFIER_GCODE_NUMBER_OFFSET)) + 710)) { // CODE IS M710, M=2, INDICATES CONFIGURATION WAS RECEIVED + Head_Not_configured = false; // HEAD SETUP COMPLETE + send_gcode_count = true; // Report the current received gcode count to the host (debugging) + } + } + + // If present, add parameters to the received gcode + if (parameter_counter && CanRxHeader.DataLength && ((identifier >> IDENTIFIER_PARAMTER1_OFFSET) & IDENTIFIER_PARAMETER_MASK)) { // Get 1st parameter, make sure it's not empty. + codeP += strlen(codeP); // There is already data in the buffer, adjust the pointer + CAN_Add_Parameters(identifier & IDENTIFIER_PARAMETER_MASK, *(float*)(can_rxbuf), codeP); + parameter_counter--; + + if (parameter_counter && (CanRxHeader.DataLength == FDCAN_DLC_BYTES_8) && ((identifier >> IDENTIFIER_PARAMTER2_OFFSET) & IDENTIFIER_PARAMETER_MASK)) { // Get 2nd parameter, make sure it's not empty. + codeP += strlen(codeP); + CAN_Add_Parameters((identifier >> IDENTIFIER_PARAMTER2_OFFSET) & IDENTIFIER_PARAMETER_MASK, *(float*)(can_rxbuf + 4), codeP); + parameter_counter--; + } + } + + if (parameter_counter == 0) { // Gcode is complete, process the gcode + gcode_counter++; + + uint32_t ms = millis(); + bool queue_status; + do { // BLOCKING! Wait for free Marlin command buffer for max 50ms + queue_status = queue.enqueue_one(can_gcode_buffer); + } + while (!queue_status && (millis() - ms < 50)); + + if (!queue_status) // Could not store the received CAN command in Marlin command buffer + CAN_Error_Code |= CAN_ERROR_MARLIN_CMD_BUFFER_OVERFLOW; + } + + return HAL_OK; +} + +void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) { // ISR! Override "weak" function + if (RxFifo0ITs & FDCAN_IT_RX_FIFO0_MESSAGE_LOST) { // Check the error status first, it might be cleared by reading a message + CAN_Error_Code |= CAN_ERROR_FIFO_OVERFLOW; + __HAL_FDCAN_CLEAR_FLAG(hfdcan, FDCAN_IT_RX_FIFO0_MESSAGE_LOST); // Clear interrupt flag + } + + if (RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) + CAN_Receive(FDCAN_RX_FIFO0); +} + +void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs) { // ISR! Override "weak" function + if (RxFifo1ITs & FDCAN_IT_RX_FIFO1_MESSAGE_LOST) { // Check the error status first, it might be cleared by reading a message + CAN_Error_Code |= CAN_ERROR_FIFO_OVERFLOW; + __HAL_FDCAN_CLEAR_FLAG(hfdcan, FDCAN_IT_RX_FIFO1_MESSAGE_LOST); // Clear interrupt flag + } + +if (RxFifo1ITs & FDCAN_IT_RX_FIFO1_NEW_MESSAGE) + CAN_Receive(FDCAN_RX_FIFO1); +} + +void TIM16_IRQHandler(void) { // FDCAN INTERRUPT HANDLER, OVERRIDE WEAK FUNCTION + if ((HardwareTimer_Handle[TIMER16_INDEX]) && (HardwareTimer_Handle[TIMER16_INDEX]->handle.Instance->SR & HAL_TIM_FLAG_ALL)) // Interrupt was caused by timer + HAL_TIM_IRQHandler(&HardwareTimer_Handle[TIMER16_INDEX]->handle); // Forward to timer interrupt handler + + uint32_t RxFifoITs = hfdcan2.Instance->IR & FDCAN_RX_FIFO_MASK; + RxFifoITs &= hfdcan2.Instance->IE; + + if (RxFifoITs) // Any Fifo read interrupts? + HAL_FDCAN_IRQHandler(&hfdcan2); // Forward call to FDCAN interrupt handler +} + +void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* fdcanHandle) { // Called automatically, configure GPIO for FDCAN, enable interrupts + GPIO_InitTypeDef GPIO_InitStruct = {0}; + RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; + + PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_FDCAN; + PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_PCLK1; + + if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) + Error_Handler(); + + // FDCAN1/2 clock enable (1 clock for both CAN devices) + if (__HAL_RCC_FDCAN_IS_CLK_DISABLED()) + __HAL_RCC_FDCAN_CLK_ENABLE(); + + if (__HAL_RCC_GPIOB_IS_CLK_DISABLED()) + __HAL_RCC_GPIOB_CLK_ENABLE(); // ENABLE GPIO B CLOCK + // FDCAN2 GPIO Configuration + // PB0 ------> FDCAN2_RX + // PB1 ------> FDCAN2_TX + + GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1; // Pin PB0 and Pin PB1 + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF3_FDCAN2; + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // Port B + + // FDCAN2 interrupt Init + HAL_NVIC_SetPriority(TIM16_FDCAN_IT0_IRQn, 1, 1); // Set interrupt priority + HAL_NVIC_EnableIRQ(TIM16_FDCAN_IT0_IRQn); // Enable interrupt handler +} + +/* +uint32_t ClockDivider FDCAN kernel clock divider (common to all FDCAN instances, applied only at initialisation of first FDCAN instance) + 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 +uint32_t FrameFormat FDCAN_FRAME_CLASSIC / FDCAN_FRAME_FD_NO_BRS / FDCAN_FRAME_FD_BRS (BRS=Bit Rate Switch) +uint32_t Mode; FDCAN_MODE_NORMAL / FDCAN_MODE_RESTRICTED_OPERATION / FDCAN_MODE_BUS_MONITORING / FDCAN_MODE_INTERNAL_LOOPBACK / FDCAN_MODE_EXTERNAL_LOOPBACK +FunctionalState AutoRetransmission Retransmit if fault is deteced, values: ENABLE / DISABLE +FunctionalState TransmitPause Waits for 2 bit times before transmitting the next frame to allow other notes to cut in, values: ENABLE / DISABLE +FunctionalState ProtocolException Protocol Exception Handling, values: ENABLE(start bus integration) or DISABLE(send error frame) +uint32_t NominalPrescaler Clock divider for generating the bit time quanta (1-512) +uint32_t NominalSyncJumpWidth Maximum number of time quanta the FDCAN allows to lengthen or shorten a bit to perform resynchronization (1-128) +uint32_t NominalTimeSeg1 Number of time quanta in Bit Segment 1 (2-256) +uint32_t NominalTimeSeg2 Number of time quanta in Bit Segment 2 (2-128) +uint32_t DataPrescaler Clock divider for generating the data bit time quanta (1-32) +uint32_t DataSyncJumpWidth Max of time quanta allowed to lengthen or shorten a data bit to perform resynchronization (1-16) +uint32_t DataTimeSeg1 Number of time quanta in Data Bit Segment 1. (1-32) +uint32_t DataTimeSeg2 Number of time quanta in Data Bit Segment 2. (1-16) +uint32_t StdFiltersNbr Number of standard Message ID filters. (0-28) +uint32_t ExtFiltersNbr Number of extended Message ID filters. (0-8) +uint32_t TxFifoQueueMode Tx FIFO/Queue Mode selection FDCAN_TX_FIFO_OPERATION / FDCAN_TX_QUEUE_OPERATION +*/ + +HAL_StatusTypeDef FDCAN2_Start(void) { // START THE FDCAN BUS + HAL_StatusTypeDef status = HAL_OK; + + // FDCAN baud rate = Device Clock Frequency / Clock Divider / Prescaler / SJW + TSG1 + TSG2) + // Baud rate = 64M / 1 / 4 / (1 + 10 + 5) = 1M + // Baud rate = 64M / 1 / 8 / (1 + 10 + 5) = 500k + // http://www.bittiming.can-wiki.info + // https://www.kvaser.com/support/calculators/can-fd-bit-timing-calculator + hfdcan2.Instance = FDCAN2; // FDCAN2 device + hfdcan2.Init.ClockDivider = FDCAN_CLOCK_DIV1; // Clock divider 1 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 + hfdcan2.Init.FrameFormat = FDCAN_FRAME_CLASSIC; // FDCAN_FRAME_CLASSIC / FDCAN_FRAME_FD_BRS / FDCAN_FRAME_FD_NO_BRS (Bit Rate Switching) + hfdcan2.Init.Mode = FDCAN_MODE_NORMAL; // FDCAN_MODE_NORMAL / FDCAN_MODE_EXTERNAL_LOOPBACK / FDCAN_MODE_INTERNAL_LOOPBACK / FDCAN_MODE_BUS_MONITORING / FDCAN_MODE_RESTRICTED_OPERATION + hfdcan2.Init.AutoRetransmission = DISABLE; // Auto Retransmission of message if error occured, can cause lockup + hfdcan2.Init.TransmitPause = DISABLE; // Transmit Pause to allow for other device to send message + hfdcan2.Init.ProtocolException = DISABLE; // ProtocolException + + hfdcan2.Init.NominalPrescaler = hfdcan2.Init.DataPrescaler = 4; // Arbitration/data clock prescaler (classic only) + hfdcan2.Init.NominalSyncJumpWidth = hfdcan2.Init.DataSyncJumpWidth = 1; // Arbitration/data sync jump width + hfdcan2.Init.NominalTimeSeg1 = hfdcan2.Init.DataTimeSeg1 = 10; // Arbitration/data period 1 + hfdcan2.Init.NominalTimeSeg2 = hfdcan2.Init.DataTimeSeg2 = 5; // Arbitration period 2 + + hfdcan2.Init.StdFiltersNbr = 2; // Number of standard frame filters + hfdcan2.Init.ExtFiltersNbr = 2; // Number of extended frame filters + hfdcan2.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; // Queue mode: FDCAN_TX_FIFO_OPERATION / FDCAN_TX_QUEUE_OPERATION + + status = HAL_FDCAN_Init(&hfdcan2); + if (status != HAL_OK) + return status; + + FDCAN_FilterTypeDef sFilterConfig; // Configure RX message filter + + sFilterConfig.IdType = FDCAN_EXTENDED_ID; // Filter to FIFO1 if higest ID bit is set + sFilterConfig.FilterIndex = 0; // Exteneded filter ID 0 + sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE + sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1; // Filter to FIFO1 + sFilterConfig.FilterID1 = 0x10000000; // Range: 0 - 0x1FFF FFFF, 0=don'tcare + sFilterConfig.FilterID2 = 0x10000000; // Range: 0 - 0x1FFF FFFF, 0=don'tcare + HAL_FDCAN_ConfigFilter(&hfdcan2, &sFilterConfig); + + sFilterConfig.IdType = FDCAN_EXTENDED_ID; // Filter all remaining messages to FIFO0 + sFilterConfig.FilterIndex = 1; // Exteneded filter ID 1 + sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE + sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; // Remaining messages go to FIFO1 + sFilterConfig.FilterID1 = 0; // Range: 0 - 0x1FFF FFFF, 0=don'tcare + sFilterConfig.FilterID2 = 0; // Range: 0 - 0x1FFF FFFF, 0=don'tcare + HAL_FDCAN_ConfigFilter(&hfdcan2, &sFilterConfig); + + sFilterConfig.IdType = FDCAN_STANDARD_ID; // Filter to FIFO1 if higest ID bit is set + sFilterConfig.FilterIndex = 0; // Standard filter ID 0 + sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE + sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1; + sFilterConfig.FilterID1 = 0b10000000000; // Range: 0 - 0x7FF, 0=don't care + sFilterConfig.FilterID2 = 0b10000000000; // Range: 0 - 0x7FF, 0=don't care + HAL_FDCAN_ConfigFilter(&hfdcan2, &sFilterConfig); + + sFilterConfig.IdType = FDCAN_STANDARD_ID; // Filter all remaining messages to FIFO0 + sFilterConfig.FilterIndex = 1; // Standard filter ID 1 + sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE + sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; // Remaining to FIFO1 + sFilterConfig.FilterID1 = 0; // Range: 0 - 0x7FF, 0=don't care + sFilterConfig.FilterID2 = 0; // Range: 0 - 0x7FF, 0=don't care + HAL_FDCAN_ConfigFilter(&hfdcan2, &sFilterConfig); + +// Configure global filter +// status = HAL_FDCAN_ConfigGlobalFilter(&hfdcan2, FDCAN_REJECT, FDCAN_REJECT, FDCAN_REJECT_REMOTE, FDCAN_REJECT_REMOTE); + + // Start the FDCAN module + status = HAL_FDCAN_Start(&hfdcan2); // ===== START FDCAN2 DEVICE ===== + + // Activate RX FIFO0 new message interrupt + if (status == HAL_OK) + status = HAL_FDCAN_ActivateNotification(&hfdcan2, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0); // Calls TIM16_IRQHandler + + // Activate RX FIFO0 message lost interrupt + if (status == HAL_OK) + status = HAL_FDCAN_ActivateNotification(&hfdcan2, FDCAN_IT_RX_FIFO0_MESSAGE_LOST, 0); // Calls TIM16_IRQHandler + + // Activate RX FIFO1 new message interrupt + if (status == HAL_OK) + status = HAL_FDCAN_ActivateNotification(&hfdcan2, FDCAN_IT_RX_FIFO1_NEW_MESSAGE, 0); // Calls TIM16_IRQHandler + + // Activate RX FIFO1 message lost interrupt + if (status == HAL_OK) + status = HAL_FDCAN_ActivateNotification(&hfdcan2, FDCAN_IT_RX_FIFO1_MESSAGE_LOST, 0); // Calls TIM16_IRQHandler + + return status; +} + +/* ---INTERRUPT HANDLERS------------------------------------------------------------- +void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs); +void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs); +void HAL_FDCAN_RxBufferNewMessageCallback(FDCAN_HandleTypeDef *hfdcan); +void HAL_FDCAN_HighPriorityMessageCallback(FDCAN_HandleTypeDef *hfdcan); + +void HAL_FDCAN_TxEventFifoCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t TxEventFifoITs); +void HAL_FDCAN_TxFifoEmptyCallback(FDCAN_HandleTypeDef *hfdcan); +void HAL_FDCAN_TxBufferCompleteCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t BufferIndexes); +void HAL_FDCAN_TxBufferAbortCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t BufferIndexes); + +void HAL_FDCAN_ErrorCallback(FDCAN_HandleTypeDef *hfdcan); +void HAL_FDCAN_ErrorStatusCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ErrorStatusITs); + +void HAL_FDCAN_ClockCalibrationCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ClkCalibrationITs); +void HAL_FDCAN_TimestampWraparoundCallback(FDCAN_HandleTypeDef *hfdcan); +void HAL_FDCAN_TimeoutOccurredCallback(FDCAN_HandleTypeDef *hfdcan); +void HAL_FDCAN_TT_ScheduleSyncCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t TTSchedSyncITs); +void HAL_FDCAN_TT_TimeMarkCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t TTTimeMarkITs); +void HAL_FDCAN_TT_StopWatchCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t SWTime, uint32_t SWCycleCount); +void HAL_FDCAN_TT_GlobalTimeCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t TTGlobTimeITs); */ + +HAL_StatusTypeDef CAN_Send_Message(bool TempUpdate) { // Called from temperature ISR! + // Send a IO/temp report from the HEAD to the HOST + HAL_StatusTypeDef status = HAL_OK; + FDCAN_TxHeaderTypeDef CanTxHeader; // Transmit CAN message buffer + + // Initialize standard TxHeader values + CanTxHeader.FDFormat = FDCAN_CLASSIC_CAN; // FDCAN_CLASSIC_CAN / FDCAN_FD_CAN + CanTxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; // FDCAN_NO_TX_EVENTS / FDCAN_STORE_TX_EVENTS + CanTxHeader.MessageMarker = 0; // 0-0xFF for tracking messages in FIFO buffer + CanTxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE; // FDCAN_ESI_PASSIVE / FDCAN_ESI_ACTIVE ??? + CanTxHeader.TxFrameType = FDCAN_DATA_FRAME; // FDCAN_DATA_FRAME / FDCAN_REMOTE_FRAME + CanTxHeader.IdType = FDCAN_STANDARD_ID; // FDCAN_STANDARD_ID / FDCAN_EXTENDED_ID + CanTxHeader.BitRateSwitch = FDCAN_BRS_OFF; // FDCAN_BRS_OFF / FDCAN_BRS_ON + + uint8_t can_tx_buffer[8]; + if (TempUpdate) { // Add temperature to CAN message, 4 bytes + float * fp = (float *)can_tx_buffer; // Point to CAN tx buffer + *fp = thermalManager.degHotend(0); // Copy temp to can_tx_buffer + CanTxHeader.DataLength = FDCAN_DLC_BYTES_4; // Hotend temp in data only, bytes, FDCAN_DLC_BYTES_4 = (4 << 16)! + } + else + CanTxHeader.DataLength = FDCAN_DLC_BYTES_0; // 0 data byte CAN message + bool request_time_sync = CAN_request_time_sync; + uint32_t VirtualIO = (Head_Not_configured << CAN_REQUEST_SETUP_BIT) | // Report Head is not configured + (READ(Z_MIN_PROBE_PIN) << CAN_PROBE_BIT) | // Report probe status + (READ(FILAMENT_RUNOUT_PIN) << CAN_FILAMENT_BIT) | // Report filament detector status + (request_time_sync << CAN_REQUEST_TIME_SYNC_BIT) | + ((!!CAN_Error_Code) << CAN_ERROR_BIT); // Report error (if any) + // IRON, ADD PINS YOU WANT TO MONITOR HERE + + CanTxHeader.Identifier = ((CanTxHeader.Identifier ^ STDID_FIFO_BIT) & STDID_FIFO_BIT) + VirtualIO; // Toggle FIFO bit for receiver filtering + uint32_t ms = millis(); + while ((HAL_FDCAN_GetTxFifoFreeLevel(&hfdcan2) == 0) && (millis() - ms < 25)) { } // BLOCKING! Wait max 50ms for free TX FIFO slot + + if (request_time_sync) { // For time sync, wait until all message TX slots are empty before sending + CAN_request_time_sync = false; + ms = millis(); + while ((HAL_FDCAN_GetTxFifoFreeLevel(&hfdcan2) < 3) && (millis() - ms < 25)) { } // BLOCKING! Wait max 50ms for free TX FIFO slot + t[0] = micros(); // Store time sync message send time + } + status = HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan2, &CanTxHeader, can_tx_buffer); // Send FDCAN message + CAN_message_counter++; + return status; +} + +HAL_StatusTypeDef CAN_Send_String(const char * message) { // Send string message to host, string should end on '\n' + HAL_StatusTypeDef status = HAL_OK; + FDCAN_TxHeaderTypeDef CanTxHeader; // Transmit CAN message buffer + + // Initialize standard TxHeader values + CanTxHeader.FDFormat = FDCAN_CLASSIC_CAN; // FDCAN_CLASSIC_CAN / FDCAN_FD_CAN + CanTxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; // FDCAN_NO_TX_EVENTS / FDCAN_STORE_TX_EVENTS + CanTxHeader.MessageMarker = 0; // 0-0xFF for tracking messages in FIFO buffer + CanTxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE; // FDCAN_ESI_PASSIVE / FDCAN_ESI_ACTIVE ??? + CanTxHeader.TxFrameType = FDCAN_DATA_FRAME; // FDCAN_DATA_FRAME / FDCAN_REMOTE_FRAME + CanTxHeader.IdType = FDCAN_STANDARD_ID; // FDCAN_STANDARD_ID / FDCAN_EXTENDED_ID + CanTxHeader.BitRateSwitch = FDCAN_BRS_OFF; // FDCAN_BRS_OFF / FDCAN_BRS_ON + + uint8_t can_tx_buffer[8]; + int remaining = strlen(message); // Length of string + int index = 0; + + #ifdef CAN_DEBUG + SERIAL_ECHOPGM(">>> SENDING STRING MESSAGE TO HOST: ", message); // IRON, SHOULD HAVE '\n' AT THE END, DEBUGGING + #endif + + while (remaining > 0) { // Keep sending message if there are more characters to send + int c = MIN(8, remaining); // Max 8 character at a time + remaining -= c; // Reduce character counter + CanTxHeader.DataLength = (c << DATALENGTH_OFFSET); // Max message length is 8 bytes, offset is 16 bits into the DataLength variable + + for (int i = 0; i < c; i++) + can_tx_buffer[i] = message[index++]; // Copy string data to CAN buffer + + uint32_t VirtualIO = (READ(Z_MIN_PROBE_PIN) << CAN_PROBE_BIT) | // Report probe status + (READ(FILAMENT_RUNOUT_PIN) << CAN_FILAMENT_BIT) | // Report filament detector status + ((!!CAN_Error_Code) << CAN_ERROR_BIT) | // Report error (if any) + (1 << CAN_STRING_MESSAGE_BIT); // Set string message bit + + CanTxHeader.Identifier = ((CanTxHeader.Identifier ^ 0b10000000000) & 0b10000000000) + VirtualIO; // Toggle FIFO bit for receiver filtering + uint32_t ms = millis(); + while ((HAL_FDCAN_GetTxFifoFreeLevel(&hfdcan2) < 2) && (millis() - ms < 50)) { } // BLOCKING! Wait for free TX FIFO slot, with 1 spare slot for urgent messages + + status = HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan2, &CanTxHeader, can_tx_buffer); // Send message + CAN_message_counter++; + } + + return status; +} + +uint32_t last_led_flash = 0; // IRON, for error LED flashing +uint32_t last_error_message = 0; +uint32_t oldExtuder_auto_fan_speed = controllerFan.settings.extruder_auto_fan_speed; + +void FDCAN_idle() { // Called from MarlinCore.cpp +// heart beat signal, send and receive +// error monitoring (hotend auto fan speed) + + if (oldExtuder_auto_fan_speed != controllerFan.settings.extruder_auto_fan_speed) { + controllerFan.settings.extruder_auto_fan_speed = MAX(controllerFan.settings.extruder_auto_fan_speed, PROBING_AUTO_FAN_SPEED); // Should not be required, safeguard! + oldExtuder_auto_fan_speed = controllerFan.settings.extruder_auto_fan_speed; + } + + if (send_gcode_count) { + MString<24> buffer(F("GCODES="), gcode_counter, '\n'); + CAN_Send_String(buffer); // Report number of gcodes received from host + send_gcode_count = false; + SERIAL_ECHOLNPGM(">>> CAN messages sent: ", CAN_message_counter); + } + // NTP style time sync + // t[0] = local time sync request time + // t[1] = host time sync receive time + // t[2] = host time sync reply time + // t[3] = local time stamp receive time + if (t[3] != 0) { + t[0] += time_offset; // Adjust local time stamps with time_offset + t[3] += time_offset; // Adjust local time stamps with time_offset + uint32_t local_time_adjustment = (t[1] - t[0] + t[2] - t[3]) >> 1; + time_offset += local_time_adjustment; + uint32_t Round_trip_delay = (t[3] - t[0] - t[2] + t[1]); + SERIAL_ECHOLNPGM("t0: ", t[0], " us"); + SERIAL_ECHOLNPGM("t1: ", t[1], " us"); + SERIAL_ECHOLNPGM("t2: ", t[2], " us"); + SERIAL_ECHOLNPGM("t3: ", t[3], " us"); + SERIAL_ECHOPGM("Local time adjustment: ", local_time_adjustment, " us"); + if (t[4]) { + SERIAL_ECHOLNPGM(" after ", ftostr42_52(float(t[0] - t[4]) / 1000000.0), " seconds"); + SERIAL_ECHOLNPGM("Drift: ", local_time_adjustment / (float(t[0] - t[4]) / 1000000.0), " us/s"); + } + else + SERIAL_EOL(); + + SERIAL_ECHOLNPGM("Time offset: ", time_offset, " us"); + SERIAL_ECHOLNPGM("Round_trip_delay: ", Round_trip_delay, " us"); + SERIAL_ECHOLNPGM("Host response time: ", (t[2] - t[1]), " us"); + + t[4] = t[0]; // Store previous time sync request time + t[3] = 0; + } + + if (hfdcan2.ErrorCode) { + uint32_t ms = millis(); + + if ((ms - last_error_message) > 20000) { // 20 seconds repeat + MString<40> buffer(F("Error: fdcan2.ErrorCode="), hfdcan2.ErrorCode, '\n'); + CAN_Send_String(buffer); + last_error_message = ms; + } + } + + if (CAN_Error_Code) { + uint32_t ms = millis(); + + if ((ms - last_led_flash) > 100) { // Flash LED fast on error + digitalToggle(LED_PIN); + last_led_flash = ms; + } + + if (CAN_Error_Code != Old_Error_Code) { // Show message on new error code + switch(CAN_Error_Code) { + case CAN_ERROR_FIFO_OVERFLOW: + CAN_Send_String("Error: HEAD CAN FIFO OVERFLOW\n"); + SERIAL_ECHOLNPGM(">>> ERROR: HEAD CAN FIFO OVERFLOW"); + break; + + case CAN_ERROR_INCOMPLETE_GCODE_RECEIVED: + CAN_Send_String("Error: HEAD INCOMPLETE GCODE MESSAGE\n"); + SERIAL_ECHOLNPGM(">>> ERROR: HEAD INCOMPLETE GCODE MESSAGE"); + break; + + case CAN_ERROR_MARLIN_CMD_BUFFER_OVERFLOW: + CAN_Send_String("Error: HEAD MARLIN CMD BUF OVERFLOW\n"); + SERIAL_ECHOLNPGM(">>> ERROR: HEAD MARLIN CMD BUF OVERFLOW"); + break; + + default: { } + } // Switch + + Old_Error_Code = CAN_Error_Code; + } // New error code + } // CAN_Error_Code +} // CAN_idle() + +/* +HAL_StatusTypeDef HAL_FDCAN_ConfigInterruptLines(FDCAN_HandleTypeDef *hfdcan, uint32_t ITList, uint32_t InterruptLine); +HAL_StatusTypeDef HAL_FDCAN_TT_ConfigInterruptLines(FDCAN_HandleTypeDef *hfdcan, uint32_t TTITList, uint32_t InterruptLine); +HAL_StatusTypeDef HAL_FDCAN_ActivateNotification(FDCAN_HandleTypeDef *hfdcan, uint32_t ActiveITs, uint32_t BufferIndexes); +HAL_StatusTypeDef HAL_FDCAN_DeactivateNotification(FDCAN_HandleTypeDef *hfdcan, uint32_t InactiveITs); +HAL_StatusTypeDef HAL_FDCAN_TT_ActivateNotification(FDCAN_HandleTypeDef *hfdcan, uint32_t ActiveTTITs); +HAL_StatusTypeDef HAL_FDCAN_TT_DeactivateNotification(FDCAN_HandleTypeDef *hfdcan, uint32_t InactiveTTITs); +void HAL_FDCAN_IRQHandler(FDCAN_HandleTypeDef *hfdcan); +*/ + +#endif // CAN_TOOLHEAD \ No newline at end of file diff --git a/Marlin/src/HAL/shared/CAN.h b/Marlin/src/HAL/shared/CAN.h index c8e410241293..c15bbb854364 100644 --- a/Marlin/src/HAL/shared/CAN.h +++ b/Marlin/src/HAL/shared/CAN.h @@ -30,17 +30,17 @@ extern uint32_t CAN_io_state; // CAN virtual IO variable -#define CAN_IO_MASK 0b11111 // Masks for the 5 virtual IO bits -#define CAN_PROBE_MASK 1 // Virtual IO bit -#define CAN_FILAMENT_MASK 2 // Virtual IO bit -#define CAN_X_ENDSTOP_MASK 4 // Virtual IO bit -#define CAN_Y_ENDSTOP_MASK 8 // Virtual IO bit -#define CAN_Z_ENDSTOP_MAS 16 // Virtual IO bit -#define CAN_STRING_MESSAGE_MASK 32 // Signals the head sends a string message -#define CAN_REQUEST_SETUP_MASK 64 // Signals the head requests setup information -#define CAN_TMC_OT_MASK 128 // Signals the head signals a TMC Over Temp error -#define CAN_E0_TARGET_MASK 256 // Signals E0 or E1 -#define CAN_ERROR_MASK 512 // Signals the head encountered an error +#define CAN_IO_MASK 0b11111 // Masks for the 5 virtual IO bits +#define CAN_PROBE_MASK 1 // Virtual IO bit +#define CAN_FILAMENT_MASK 2 // Virtual IO bit +#define CAN_X_ENDSTOP_MASK 4 // Virtual IO bit +#define CAN_Y_ENDSTOP_MASK 8 // Virtual IO bit +#define CAN_Z_ENDSTOP_MAS 16 // Virtual IO bit +#define CAN_STRING_MESSAGE_MASK 32 // Signals the head sends a string message +#define CAN_REQUEST_SETUP_MASK 64 // Signals the head requests setup information +#define CAN_TMC_OT_MASK 128 // Signals the head signals a TMC Over Temp error +#define CAN_CAN_REQUEST_TIME_SYNC_MASK 256 // Signals the head requested a time sync +#define CAN_ERROR_MASK 512 // Signals the head encountered an error HAL_StatusTypeDef CAN1_Start(); // FUNCTION PROTOTYPES HAL_StatusTypeDef CAN1_Stop(); diff --git a/Marlin/src/HAL/shared/FDCAN.h b/Marlin/src/HAL/shared/FDCAN.h new file mode 100644 index 000000000000..6a8712deea01 --- /dev/null +++ b/Marlin/src/HAL/shared/FDCAN.h @@ -0,0 +1,48 @@ +#pragma once + +// IMPORTANT NOTES +// =============== +// In \Users\\.platformio\packages\framework-arduinoststm32@4.20600.231001\libraries\SrcWrapper\src\HardwareTimer.cpp +// Add function "__weak" in front of "void TIM16_IRQHandler(void)" +// NOTE: Every CAN message is accepted in FIFO0 if: 1. No Standard/Extended filters are set. 2. No global filters is set. + +#define CAN_IO_MASK 0b11111 // Virtual IO Mask, 5 bits are used for IO signalling +#define CAN_PROBE_BIT 0 // Virtual IO bit +#define CAN_FILAMENT_BIT 1 // Virtual IO bit +#define CAN_X_ENDSTOP_BIT 2 // Virtual IO bit +#define CAN_Y_ENDSTOP_BIT 3 // Virtual IO bit +#define CAN_Z_ENDSTOP_BIT 4 // Virtual IO bit +#define CAN_STRING_MESSAGE_BIT 5 // Signals the head sends a string message +#define CAN_REQUEST_SETUP_BIT 6 // Signals the head requests setup information +#define CAN_TMC_OT_BIT 7 // Signals the head encountered a TMC Over Temp error +#define CAN_REQUEST_TIME_SYNC_BIT 8 // Signals a request for time sync +#define CAN_ERROR_BIT 9 // Signals the head encountered an error + +#define GCODE_TYPE_D 0 +#define GCODE_TYPE_G 1 +#define GCODE_TYPE_M 2 +#define GCODE_TYPE_T 3 + +#define IDENTIFIER_PARAMTER1_OFFSET 0 +#define IDENTIFIER_PARAMTER2_OFFSET 5 +#define IDENTIFIER_GCODE_NUMBER_OFFSET 10 +#define IDENTIFIER_GCODE_TYPE_OFFSET 23 +#define IDENTIFIER_PARAMETER_COUNT_OFFSET 25 + +#define DATALENGTH_OFFSET 16 + +#define IDENTIFIER_PARAMETER_COUNT_MASK 0b111 +#define IDENTIFIER_PARAMETER_MASK 0b11111 +#define IDENTIFIER_GCODE_TYPE_MASK 0b11 // GCODE TYPE +#define IDENTIFIER_GCODE_NUMBER_MASK 0b1111111111111 // GCODE NUMBER ONLY +#define IDENTIFIER_GCODE_MASK 0b111111111111111 // GCODE TYPE AND NUMBER + +#define STDID_FIFO_BIT 0b10000000000 + +// ERROR CODES +#define CAN_ERROR_FIFO_OVERFLOW 1 +#define CAN_ERROR_INCOMPLETE_GCODE_RECEIVED 2 +#define CAN_ERROR_MARLIN_CMD_BUFFER_OVERFLOW 4 + +HAL_StatusTypeDef CAN_Send_String(const char * message); // Send CAN string to host +HAL_StatusTypeDef FDCAN2_Start(void); // Start the CAN bus \ No newline at end of file diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index d04600383323..60fbcdac23d7 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -38,6 +38,10 @@ #include "HAL/shared/CAN.h" #endif +#if ENABLED(CAN_TOOLHEAD) + #include "HAL/shared/FDCAN.h" +#endif + #if ENABLED(WIFISUPPORT) #include "HAL/shared/esp_wifi.h" #endif @@ -886,6 +890,16 @@ void idle(const bool no_stepper_sleep/*=false*/) { // Manage Fixed-time Motion Control TERN_(FT_MOTION, ftMotion.loop()); +#if ENABLED(CAN_MASTER) + void CAN_idle(); // Function Prototype + CAN_idle(); // Call CAN idle task +#endif + +#if ENABLED(CAN_TOOLHEAD) + void FDCAN_idle(); // Function prototype + FDCAN_idle(); // Call FDCAN idle task +#endif // CAN_TOOLHEAD + IDLE_DONE: TERN_(MARLIN_DEV_MODE, idle_depth--); @@ -1194,8 +1208,7 @@ void setup() { while (!MYSERIAL3.connected() && PENDING(millis(), serial_connect_timeout)) { /*nada*/ } #endif #endif - - SERIAL_ECHOLNPGM(TERN(CAN_MASTER, "\nstart\n", "start")); + SERIAL_ECHOLNPGM("start\n"); #if ENABLED(CAN_MASTER) SERIAL_ECHOLN( @@ -1204,6 +1217,13 @@ void setup() { ); #endif + #if ENABLED(CAN_TOOLHEAD) + SERIAL_ECHOLN( + F(">>> FDCAN2 Start: "), + FDCAN2_Start() == HAL_OK ? F("OK") : F("FAILED!") + ); + #endif + // Set up these pins early to prevent suicide #if HAS_KILL SETUP_LOG("KILL_PIN"); diff --git a/Marlin/src/gcode/temp/M306.cpp b/Marlin/src/gcode/temp/M306.cpp index 4b4b69c287dc..511106201fe0 100644 --- a/Marlin/src/gcode/temp/M306.cpp +++ b/Marlin/src/gcode/temp/M306.cpp @@ -80,6 +80,9 @@ void GcodeSuite::M306() { thermalManager.MPC_autotune(e, tuning_type); ui.reset_status(); + #if ENABLED(CAN_TOOLHEAD) + M306_report(true); // Report M306 settings to CAN host + #endif #endif return; @@ -125,6 +128,24 @@ 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), + '\n'); + + HAL_StatusTypeDef CAN_Send_String(const char * message); // Function Prototype + CAN_Send_String(buffer); + } + #endif // CAN_TOOLHEAD } #endif // MPCTEMP diff --git a/Marlin/src/module/endstops.cpp b/Marlin/src/module/endstops.cpp index dcb10c014edd..b4b629d5b804 100644 --- a/Marlin/src/module/endstops.cpp +++ b/Marlin/src/module/endstops.cpp @@ -677,6 +677,12 @@ void Endstops::update() { // When closing the gap check the enabled probe if (probe_switch_activated()) UPDATE_LIVE_STATE(Z, TERN(USE_Z_MIN_PROBE, MIN_PROBE, MIN)); + + #if ENABLED(CAN_TOOLHEAD) + HAL_StatusTypeDef CAN_Send_Message(bool TempUpdate); // Function Prototype + CAN_Send_Message(false); // Send Virtual IO update without temperature report + #endif // CAN_TOOLHEAD + #endif #if USE_Z_MAX diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp index fa984176d8a5..b2b80893cedd 100644 --- a/Marlin/src/module/temperature.cpp +++ b/Marlin/src/module/temperature.cpp @@ -4431,6 +4431,13 @@ void Temperature::isr() { // Poll endstops state, if required endstops.poll(); +#if ENABLED(CAN_TOOLHEAD) + HAL_StatusTypeDef CAN_Send_Message(bool TempUpdate); // Function Prototype + static uint32_t loopCounter = 0; + if ((loopCounter++ % 512) == 0) // Update E0 Temp every 512ms + CAN_Send_Message(true); // Send temp report with IO report +#endif // CAN_TOOLHEAD + // Periodically call the planner timer service routine planner.isr(); } diff --git a/Marlin/src/pins/pins.h b/Marlin/src/pins/pins.h index f9b0ee25235a..905646394a6c 100644 --- a/Marlin/src/pins/pins.h +++ b/Marlin/src/pins/pins.h @@ -562,7 +562,7 @@ // #elif MB(BTT_EBB42_V1_1) - #include "stm32g0/pins_BTT_EBB42_V1_1.h" // STM32G0 env:BTT_EBB42_V1_1_filament_extruder + #include "stm32g0/pins_BTT_EBB42_V1_1.h" // STM32G0 env:BTT_EBB42_V1_1_filament_extruder env:BTT_EBB42_V1_1_filament_extruder_CAN #elif MB(BTT_SKR_MINI_E3_V3_0) #include "stm32g0/pins_BTT_SKR_MINI_E3_V3_0.h" // STM32G0 env:STM32G0B1RE_btt env:STM32G0B1RE_btt_xfer #elif MB(BTT_MANTA_E3_EZ_V1_0) @@ -798,7 +798,7 @@ #elif MB(MKS_ROBIN_NANO_V3_1) #include "stm32f4/pins_MKS_ROBIN_NANO_V3.h" // STM32F4 env:mks_robin_nano_v3_1 env:mks_robin_nano_v3_1_usb_flash_drive env:mks_robin_nano_v3_1_usb_flash_drive_msc #elif MB(MKS_MONSTER8_V1) - #include "stm32f4/pins_MKS_MONSTER8_V1.h" // STM32F4 env:mks_monster8 env:mks_monster8_usb_flash_drive env:mks_monster8_usb_flash_drive_msc + #include "stm32f4/pins_MKS_MONSTER8_V1.h" // STM32F4 env:mks_monster8 env:mks_monster8_usb_flash_drive env:mks_monster8_usb_flash_drive_msc env:mks_monster8_usb_flash_drive_msc_CAN #elif MB(MKS_MONSTER8_V2) #include "stm32f4/pins_MKS_MONSTER8_V2.h" // STM32F4 env:mks_monster8 env:mks_monster8_usb_flash_drive env:mks_monster8_usb_flash_drive_msc env:mks_monster8_usb_flash_drive_msc_CAN #elif MB(ANET_ET4) diff --git a/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h b/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h index b15c7d7288c6..fefc8dccdbcc 100644 --- a/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h +++ b/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h @@ -43,8 +43,15 @@ //#define FLASH_EEPROM_EMULATION // Use Flash-based EEPROM emulation #define I2C_EEPROM // Need use jumpers set i2c for EEPROM #define MARLIN_EEPROM_SIZE 0x1000 // 4K -#define I2C_SCL_PIN PB8 // I2C_SCL and CAN_RX -#define I2C_SDA_PIN PB9 // I2C_SDA and CAN_TX + +#if ENALBED(CAN_MASTER) // Onboard I2C EEPROM is disconnected by enabling CAN bus jumpers +// NOTE: 2 Patchwires are required to reconnect the I2C EEPROM +// #define I2C_SCL_PIN PA8 // 3Dtouch servo control pin +// #define I2C_SDA_PIN PC9 // SPI3_CS TFT Card chip select pin +#else + #define I2C_SCL_PIN PB8 // I2C_SCL to CAN_RX + #define I2C_SDA_PIN PB9 // I2C_SDA to CAN_TX +#endif // // Servos diff --git a/Marlin/src/pins/stm32g0/pins_BTT_EBB42_V1_1.h b/Marlin/src/pins/stm32g0/pins_BTT_EBB42_V1_1.h index fef28f3f3682..53f88673d58a 100644 --- a/Marlin/src/pins/stm32g0/pins_BTT_EBB42_V1_1.h +++ b/Marlin/src/pins/stm32g0/pins_BTT_EBB42_V1_1.h @@ -147,3 +147,6 @@ #define BTN_EN2 PB5 #define BTN_ENC PB6 #endif + +#define CAN_RX PB0 +#define CAN_TX PB1 diff --git a/ini/stm32g0.ini b/ini/stm32g0.ini index 4a9d1e4f7ae5..c2365353b655 100644 --- a/ini/stm32g0.ini +++ b/ini/stm32g0.ini @@ -40,6 +40,22 @@ board_upload.offset_address = 0x08000000 build_flags = ${stm32_variant.build_flags} ${stm32g0_I2C2.build_flags} -flto -Wl,--no-warn-rwx-segment + +[env:BTT_EBB42_V1_1_filament_extruder_CAN] +extends = stm32_variant +platform = ststm32@17.1.0 +platform_packages = framework-arduinoststm32@~4.20600.231001 + toolchain-gccarmnoneeabi@1.120301.0 + +board = marlin_BTT_EBB42_V1_1 +board_build.offset = 0x0000 +board_upload.offset_address = 0x08000000 +build_flags = ${stm32_variant.build_flags} ${stm32g0_I2C2.build_flags} + -flto + -Wl,--no-warn-rwx-segment + -DHAL_FDCAN_MODULE_ENABLED + -DTIMER_SERIAL=TIM4 + debug_tool = stlink upload_protocol = dfu upload_command = dfu-util -a 0 -s 0x08000000:leave -D "$SOURCE" From a0ce07afe648edbffe0ebc6260aa4bb841ae7f2f Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Sun, 1 Dec 2024 00:21:02 +0800 Subject: [PATCH 05/21] Rename BTT EBB42 environment, drop "ilament_extruder" from the CAN environment Remove unneeded line from the build_flags: "-DTIMER_SERIAL=TIM4" --- Marlin/src/pins/pins.h | 2 +- ini/stm32g0.ini | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Marlin/src/pins/pins.h b/Marlin/src/pins/pins.h index 905646394a6c..89602e2916d1 100644 --- a/Marlin/src/pins/pins.h +++ b/Marlin/src/pins/pins.h @@ -562,7 +562,7 @@ // #elif MB(BTT_EBB42_V1_1) - #include "stm32g0/pins_BTT_EBB42_V1_1.h" // STM32G0 env:BTT_EBB42_V1_1_filament_extruder env:BTT_EBB42_V1_1_filament_extruder_CAN + #include "stm32g0/pins_BTT_EBB42_V1_1.h" // STM32G0 env:BTT_EBB42_V1_1_filament_extruder env:BTT_EBB42_V1_1_FDCAN #elif MB(BTT_SKR_MINI_E3_V3_0) #include "stm32g0/pins_BTT_SKR_MINI_E3_V3_0.h" // STM32G0 env:STM32G0B1RE_btt env:STM32G0B1RE_btt_xfer #elif MB(BTT_MANTA_E3_EZ_V1_0) diff --git a/ini/stm32g0.ini b/ini/stm32g0.ini index c2365353b655..70f275eb4e40 100644 --- a/ini/stm32g0.ini +++ b/ini/stm32g0.ini @@ -41,7 +41,7 @@ build_flags = ${stm32_variant.build_flags} ${stm32g0_I2C2.build_ -flto -Wl,--no-warn-rwx-segment -[env:BTT_EBB42_V1_1_filament_extruder_CAN] +[env:BTT_EBB42_V1_1_FDCAN] extends = stm32_variant platform = ststm32@17.1.0 platform_packages = framework-arduinoststm32@~4.20600.231001 @@ -54,7 +54,6 @@ build_flags = ${stm32_variant.build_flags} ${stm32g0_I2C2.build_ -flto -Wl,--no-warn-rwx-segment -DHAL_FDCAN_MODULE_ENABLED - -DTIMER_SERIAL=TIM4 debug_tool = stlink upload_protocol = dfu From dd5a2ff60f0baa51524944b32612e1c62c874c1f Mon Sep 17 00:00:00 2001 From: thisiskeithb <13375512+thisiskeithb@users.noreply.github.com> Date: Sat, 30 Nov 2024 11:33:22 -0800 Subject: [PATCH 06/21] Use BTT EBB32 base/common environment ... and reorder so CAN is primary --- Marlin/src/pins/pins.h | 2 +- ini/stm32g0.ini | 33 +++++++++++++++------------------ 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/Marlin/src/pins/pins.h b/Marlin/src/pins/pins.h index 89602e2916d1..516fe1d71a22 100644 --- a/Marlin/src/pins/pins.h +++ b/Marlin/src/pins/pins.h @@ -562,7 +562,7 @@ // #elif MB(BTT_EBB42_V1_1) - #include "stm32g0/pins_BTT_EBB42_V1_1.h" // STM32G0 env:BTT_EBB42_V1_1_filament_extruder env:BTT_EBB42_V1_1_FDCAN + #include "stm32g0/pins_BTT_EBB42_V1_1.h" // STM32G0 env:BTT_EBB42_V1_1_FDCAN env:BTT_EBB42_V1_1_filament_extruder #elif MB(BTT_SKR_MINI_E3_V3_0) #include "stm32g0/pins_BTT_SKR_MINI_E3_V3_0.h" // STM32G0 env:STM32G0B1RE_btt env:STM32G0B1RE_btt_xfer #elif MB(BTT_MANTA_E3_EZ_V1_0) diff --git a/ini/stm32g0.ini b/ini/stm32g0.ini index 70f275eb4e40..6bbb06ca742d 100644 --- a/ini/stm32g0.ini +++ b/ini/stm32g0.ini @@ -26,10 +26,9 @@ build_flags = -DPIN_WIRE_SCL=PB3 -DPIN_WIRE_SDA=PB4 # -# BigTreeTech EBB42 V1.1 (STM32G0B1CBT6 ARM Cortex-M0+) -# This board is being used to control Filament extruders. This is not supported for 3D printing, as it has no kinematics control +# BTT EBB32 V1.1 base # -[env:BTT_EBB42_V1_1_filament_extruder] +[BTT_EBB42_V1_1_common] extends = stm32_variant platform = ststm32@17.1.0 platform_packages = framework-arduinoststm32@~4.20600.231001 @@ -40,25 +39,23 @@ board_upload.offset_address = 0x08000000 build_flags = ${stm32_variant.build_flags} ${stm32g0_I2C2.build_flags} -flto -Wl,--no-warn-rwx-segment - -[env:BTT_EBB42_V1_1_FDCAN] -extends = stm32_variant -platform = ststm32@17.1.0 -platform_packages = framework-arduinoststm32@~4.20600.231001 - toolchain-gccarmnoneeabi@1.120301.0 - -board = marlin_BTT_EBB42_V1_1 -board_build.offset = 0x0000 -board_upload.offset_address = 0x08000000 -build_flags = ${stm32_variant.build_flags} ${stm32g0_I2C2.build_flags} - -flto - -Wl,--no-warn-rwx-segment - -DHAL_FDCAN_MODULE_ENABLED - debug_tool = stlink upload_protocol = dfu upload_command = dfu-util -a 0 -s 0x08000000:leave -D "$SOURCE" +# +# BigTreeTech EBB42 V1.1 (STM32G0B1CBT6 ARM Cortex-M0+) as a CAN toolboard +# +[env:BTT_EBB42_V1_1_FDCAN] +extends = BTT_EBB42_V1_1_common +build_flags = -DHAL_FDCAN_MODULE_ENABLED + +# +# BigTreeTech EBB42 V1.1 (STM32G0B1CBT6 ARM Cortex-M0+) as a filament extruder (no kinematics control) +# +[env:BTT_EBB42_V1_1_filament_extruder] +extends = BTT_EBB42_V1_1_common + # # BigTreeTech SKR Mini E3 V3.0 (STM32G0B0RET6 / STM32G0B1RET6 ARM Cortex-M0+) # From 73bb491eb780d25ed6f9c0db763aa3b48468512d Mon Sep 17 00:00:00 2001 From: thisiskeithb <13375512+thisiskeithb@users.noreply.github.com> Date: Sat, 30 Nov 2024 11:38:18 -0800 Subject: [PATCH 07/21] Format MONSTER8 pins --- Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h b/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h index fefc8dccdbcc..422374ac80b0 100644 --- a/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h +++ b/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h @@ -44,13 +44,13 @@ #define I2C_EEPROM // Need use jumpers set i2c for EEPROM #define MARLIN_EEPROM_SIZE 0x1000 // 4K -#if ENALBED(CAN_MASTER) // Onboard I2C EEPROM is disconnected by enabling CAN bus jumpers -// NOTE: 2 Patchwires are required to reconnect the I2C EEPROM -// #define I2C_SCL_PIN PA8 // 3Dtouch servo control pin -// #define I2C_SDA_PIN PC9 // SPI3_CS TFT Card chip select pin +#if ENALBED(CAN_MASTER) // Onboard I2C EEPROM is disconnected by enabling CAN bus jumpers +// NOTE: 2 Patch wires are required to reconnect the I2C EEPROM +//#define I2C_SCL_PIN PA8 // BLTouch servo control pin +//#define I2C_SDA_PIN PC9 // SPI3_CS TFT Card chip select pin #else - #define I2C_SCL_PIN PB8 // I2C_SCL to CAN_RX - #define I2C_SDA_PIN PB9 // I2C_SDA to CAN_TX + #define I2C_SCL_PIN PB8 // I2C_SCL to CAN_RX + #define I2C_SDA_PIN PB9 // I2C_SDA to CAN_TX #endif // From 20ea87f47f4549bbce7452e395a5a9ffbf1f07a5 Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Sun, 1 Dec 2024 23:50:21 +0800 Subject: [PATCH 08/21] env:BTT_EBB42_V1_1_FDCAN build_flags fix Fixes an issue in the env:BTT_EBB42_V1_1_FDCAN build_flags --- ini/stm32g0.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ini/stm32g0.ini b/ini/stm32g0.ini index 6bbb06ca742d..bf20b68f9b7a 100644 --- a/ini/stm32g0.ini +++ b/ini/stm32g0.ini @@ -48,8 +48,8 @@ upload_command = dfu-util -a 0 -s 0x08000000:leave -D "$SOURCE" # [env:BTT_EBB42_V1_1_FDCAN] extends = BTT_EBB42_V1_1_common -build_flags = -DHAL_FDCAN_MODULE_ENABLED - +build_flags = ${BTT_EBB42_V1_1_common.build_flags} + -DHAL_FDCAN_MODULE_ENABLED # # BigTreeTech EBB42 V1.1 (STM32G0B1CBT6 ARM Cortex-M0+) as a filament extruder (no kinematics control) # From adf012a7426d8a22bbdfa65d6da909b01fe643d6 Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Fri, 6 Dec 2024 09:08:21 +0800 Subject: [PATCH 09/21] Add clock difference drift calculation Show predicted time sync adjustment based on previous measurement --- Marlin/src/HAL/STM32/FDCAN.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Marlin/src/HAL/STM32/FDCAN.cpp b/Marlin/src/HAL/STM32/FDCAN.cpp index a942acdbe7c7..de066696faa8 100644 --- a/Marlin/src/HAL/STM32/FDCAN.cpp +++ b/Marlin/src/HAL/STM32/FDCAN.cpp @@ -65,6 +65,7 @@ volatile bool send_gcode_count = false; volatile uint32_t t[5] = { 0, 0, 0, 0, 0 }; // Timestamps for time sync volatile bool CAN_request_time_sync = false; // Request a timestamp volatile uint32_t time_offset = 0; // Time offset in micro seconds in relation to host time +float drift = 0; // Time sync calculated drift between host and tool head extern "C" void TIM16_IRQHandler(void); extern "C" void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs); @@ -516,10 +517,14 @@ void FDCAN_idle() { // Called from MarlinCore.cpp SERIAL_ECHOLNPGM("t1: ", t[1], " us"); SERIAL_ECHOLNPGM("t2: ", t[2], " us"); SERIAL_ECHOLNPGM("t3: ", t[3], " us"); + if (drift) + SERIAL_ECHOLNPGM("Predicted time adjustment: ", drift * float(t[0] - t[4]) / 1000000.0 , " us"); + SERIAL_ECHOPGM("Local time adjustment: ", local_time_adjustment, " us"); if (t[4]) { SERIAL_ECHOLNPGM(" after ", ftostr42_52(float(t[0] - t[4]) / 1000000.0), " seconds"); - SERIAL_ECHOLNPGM("Drift: ", local_time_adjustment / (float(t[0] - t[4]) / 1000000.0), " us/s"); + drift = local_time_adjustment / (float(t[0] - t[4]) / 1000000.0); // Calculate drift again + SERIAL_ECHOLNPGM("Drift: ", drift, " us/s"); } else SERIAL_EOL(); From 9af20e07e469095a6354fed34cdc0f29bd150cf3 Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:15:21 +0800 Subject: [PATCH 10/21] Merge upstream changes to endstops.cpp affecting CAN bus virtual IO Counter changes to endstops.cpp so CAN bus virtual IO still works --- Marlin/src/feature/runout.h | 4 ++-- Marlin/src/module/endstops.cpp | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Marlin/src/feature/runout.h b/Marlin/src/feature/runout.h index 8cbad8e61eb2..aab51cc9ac8e 100644 --- a/Marlin/src/feature/runout.h +++ b/Marlin/src/feature/runout.h @@ -55,11 +55,11 @@ #define HAS_FILAMENT_SWITCH 1 #endif -#define FILAMENT_IS_OUT() (READ(FIL_RUNOUT_PIN) == FIL_RUNOUT_STATE) - #if ENABLED(CAN_MASTER) + #define FILAMENT_IS_OUT() (bool(CAN_io_state & CAN_FILAMENT_MASK) == FIL_RUNOUT_STATE) #define RUNOUT_STATE(N) bool(CAN_io_state & CAN_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 diff --git a/Marlin/src/module/endstops.cpp b/Marlin/src/module/endstops.cpp index b4b629d5b804..83450bb73482 100644 --- a/Marlin/src/module/endstops.cpp +++ b/Marlin/src/module/endstops.cpp @@ -27,9 +27,10 @@ #include "endstops.h" #include "stepper.h" -#include "../sd/cardreader.h" -#include "temperature.h" -#include "../lcd/marlinui.h" +#if HAS_STATUS_MESSAGE + #include "../lcd/marlinui.h" +#endif + #if ENABLED(SOVOL_SV06_RTS) #include "../lcd/sovol_rts/sovol_rts.h" #endif @@ -44,6 +45,8 @@ #if ENABLED(SD_ABORT_ON_ENDSTOP_HIT) #include "printcounter.h" // for print_job_timer + #include "temperature.h" + #include "../sd/cardreader.h" #endif #if ENABLED(BLTOUCH) @@ -54,12 +57,12 @@ #include "../feature/joystick.h" #endif -#if HAS_BED_PROBE - #include "probe.h" +#if HAS_FILAMENT_SENSOR + #include "../feature/runout.h" #endif -#if HAS_FILAMENT_SENSOR && !MULTI_FILAMENT_SENSOR - #include "../feature/runout.h" +#if HAS_BED_PROBE + #include "probe.h" #endif #define DEBUG_OUT ALL(USE_SENSORLESS, DEBUG_LEVELING_FEATURE) @@ -385,13 +388,13 @@ void Endstops::event_handler() { #endif SERIAL_EOL(); - TERN_(HAS_STATUS_MESSAGE, + #if HAS_STATUS_MESSAGE ui.status_printf(0, F(S_FMT GANG_N_1(NUM_AXES, " %c") " %c"), GET_TEXT_F(MSG_LCD_ENDSTOPS), NUM_AXIS_LIST_(chrX, chrY, chrZ, chrI, chrJ, chrK, chrU, chrV, chrW) chrP - ) - ); + ); + #endif #if ENABLED(SD_ABORT_ON_ENDSTOP_HIT) if (planner.abort_on_endstop_hit) { @@ -535,11 +538,8 @@ void __O2 Endstops::report_states() { print_es_state(extDigitalRead(pin) != state); } #undef _CASE_RUNOUT - #elif HAS_FILAMENT_SENSOR - - print_es_state(RUNOUT_STATE(1) != FIL_RUNOUT1_STATE, F(STR_FILAMENT)); - + print_es_state(!FILAMENT_IS_OUT(), F(STR_FILAMENT)); #endif TERN_(BLTOUCH, bltouch._reset_SW_mode()); From 45ebd127946ae77ed97bfcefe429b69989846be4 Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Sat, 14 Dec 2024 02:22:31 +0800 Subject: [PATCH 11/21] Undo Filament sensor missing text bugfix to solve merge conflict OLD: print_es_state(!FILAMENT_IS_OUT(), F(STR_FILAMENT)); NEW: print_es_state(!FILAMENT_IS_OUT()); OLD is actually correct. --- Marlin/src/module/endstops.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marlin/src/module/endstops.cpp b/Marlin/src/module/endstops.cpp index 83450bb73482..97cbde21242a 100644 --- a/Marlin/src/module/endstops.cpp +++ b/Marlin/src/module/endstops.cpp @@ -539,7 +539,7 @@ void __O2 Endstops::report_states() { } #undef _CASE_RUNOUT #elif HAS_FILAMENT_SENSOR - print_es_state(!FILAMENT_IS_OUT(), F(STR_FILAMENT)); + print_es_state(!FILAMENT_IS_OUT()); #endif TERN_(BLTOUCH, bltouch._reset_SW_mode()); From 8d493540ee0832cce16089c3a938c94e744c5349 Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Sat, 14 Dec 2024 06:13:54 +0800 Subject: [PATCH 12/21] Added basic support for the ADXL345 3-axis accelerometer WIP! --- Marlin/Configuration_adv.h | 6 ++ Marlin/src/MarlinCore.cpp | 35 ++++--- .../src/feature/accelerometer/acc_adxl345.cpp | 73 ++++++++++++++ .../src/feature/accelerometer/acc_adxl345.h | 98 +++++++++++++++++++ ini/features.ini | 1 + 5 files changed, 199 insertions(+), 14 deletions(-) create mode 100644 Marlin/src/feature/accelerometer/acc_adxl345.cpp create mode 100644 Marlin/src/feature/accelerometer/acc_adxl345.h diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 564f44df606d..e5c6563064cc 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -3496,6 +3496,12 @@ #endif // HAS_TRINAMIC_CONFIG +// @section spibus + +// This feature is EXPERIMENTAL +// Warning: Enabling the sensor will probably conflict with the onboard SD Card +//#define HAS_ADXL345_ACCELEROMETER; // uncomment to enable the 3-axis accelerometer + // @section i2cbus // diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index 60fbcdac23d7..307f45f34011 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -42,6 +42,10 @@ #include "HAL/shared/FDCAN.h" #endif +#if ENABLED(HAS_ADXL345_ACCELEROMETER) + #include "feature/accelerometer/acc_adxl345.h" +#endif + #if ENABLED(WIFISUPPORT) #include "HAL/shared/esp_wifi.h" #endif @@ -1210,20 +1214,6 @@ void setup() { #endif SERIAL_ECHOLNPGM("start\n"); - #if ENABLED(CAN_MASTER) - SERIAL_ECHOLN( - F(">>> CAN1 Start: "), - CAN1_Start() == HAL_OK ? F("OK") : F("FAILED!") - ); - #endif - - #if ENABLED(CAN_TOOLHEAD) - SERIAL_ECHOLN( - F(">>> FDCAN2 Start: "), - FDCAN2_Start() == HAL_OK ? F("OK") : F("FAILED!") - ); - #endif - // Set up these pins early to prevent suicide #if HAS_KILL SETUP_LOG("KILL_PIN"); @@ -1268,6 +1258,23 @@ void setup() { SETUP_RUN(hal.init()); + #if ENABLED(CAN_MASTER) + SERIAL_ECHOLN( + F(">>> CAN1 Start: "), + CAN1_Start() == HAL_OK ? F("OK") : F("FAILED!") + ); + #endif + + #if ENABLED(CAN_TOOLHEAD) + SERIAL_ECHOLN( + F(">>> FDCAN2 Start: "), + FDCAN2_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 diff --git a/Marlin/src/feature/accelerometer/acc_adxl345.cpp b/Marlin/src/feature/accelerometer/acc_adxl345.cpp new file mode 100644 index 000000000000..2b061915d41d --- /dev/null +++ b/Marlin/src/feature/accelerometer/acc_adxl345.cpp @@ -0,0 +1,73 @@ +/*************************************************************** + * + * ADXL345 3-AXIS ACCELEROMETER ON SPI BUS + * 4-WIRE SPI COMMUNICATION + * Define: SD_SS_PIN, SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN + * + ****************************************************************/ + +#include "../../inc/MarlinConfig.h" + +#if ENABLED(HAS_ADXL345_ACCELEROMETER) + +#include "acc_ADXL345.h" + +#include "../../MarlinCore.h" +#include "../../HAL/shared/Delay.h" + +#include "SPI.h" +extern SPIClass SPI; + +ADXL345 adxl345; + +void ADXL345::begin() { + pinMode(SD_SS_PIN, OUTPUT); // set ADXL345 Chip Select pin to output mode + pinMode(SD_SS_PIN, HIGH); // set ADXL345 Chip Select to HIGH before configuratin SPI + spiBegin(); // sets Chip Select to HIGH (again) + spiInit(SPI_HALF_SPEED); // calls SPI.begin(), sets speed to 4MHz, max is 5MHz for ADXL345 + SPI.setDataMode(SPI_MODE3); // ADXL345 uses SPI_MODE3 to communicate (CPOL=1, CPHA = 1) + + // set range to 2g (4wire SPI, right justify data, 10-bit resolution) + writeRegister(ADXL345_DATA_FORMAT_REG, ADXL345_DATA_RANGE_2G); + + // enable measurement mode, use streaming mode + writeRegister(ADXL345_POWER_CTL_REG, ADXL345_POWER_CTL_MEASURE | ADXL345_FIFO_CTL_MODE_STREAM); + + // set to 100Hz sampling rate + writeRegister(ADXL345_RATE_REG, ADXL345_RATE_100HZ); +} + +void ADXL345::end() { // put device in standby mode + writeRegister(ADXL345_POWER_CTL_REG, ADXL345_POWER_CTL_STANDBY); +} + +void ADXL345::writeRegister(uint8_t registerAddress, uint8_t data) { + digitalWrite(SD_SS_PIN, LOW); // set Chip Select to LOW to start the write + spiSend(registerAddress); // send the register address + spiSend(data); // send the data + digitalWrite(SD_SS_PIN, HIGH); // set Chip Select to HIGH to complete the write +} + +void ADXL345::readRegister(uint8_t registerAddress, int numBytes, uint8_t * buffer) { + uint8_t address = registerAddress | 0x80; // set read bit + if (numBytes > 1) + address = address | 0x40; // also set multi-byte read if needed + + digitalWrite(SD_SS_PIN, LOW); // set Chip Select to LOW to start the read + spiSend(address); // send the register address + for (int i = 0; i < numBytes; i++) + buffer[i] = spiRec(); // read the data + digitalWrite(SD_SS_PIN, HIGH); // set Chip Select to HIGH to complete the read + delayMicroseconds(5); // allow 5us for the FIFO/registers to update (see datasheet) +} + +// get a accleration measurement for the X, Y and Z axis +void ADXL345::readMeasurement(ADXL345_measurement_t *acceleration) { + readRegister(ADXL345_DATA_X0_REG, 6, (uint8_t*)acceleration); +} + +void ADXL345::select(const bool select) { + WRITE(SD_SS_PIN, !select); // ADXL345 is selected on LOW +} + +#endif // HAS_ADXL345_ACCELEROMETER \ No newline at end of file diff --git a/Marlin/src/feature/accelerometer/acc_adxl345.h b/Marlin/src/feature/accelerometer/acc_adxl345.h new file mode 100644 index 000000000000..d890cf2dd937 --- /dev/null +++ b/Marlin/src/feature/accelerometer/acc_adxl345.h @@ -0,0 +1,98 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 + +// ADXL345 registers and defines +#define ADXL345_DEV_ID_REG 0x00 + +#define ADXL345_OFFSET_X_REG 0x1E // int8_t, 15.6mg/LSB (0x7F=2G) +#define ADXL345_OFFSET_Y_REG 0x1F // int8_t, 15.6mg/LSB (0x7F=2G) +#define ADXL345_OFFSET_Z_REG 0x20 // int8_t, 15.6mg/LSB (0x7F=2G) + +#define ADXL345_RATE_REG 0x2C // Sample rate register 0.1 - 3200Hz +#define ADXL345_RATE_0_1HZ 0x0 +#define ADXL345_RATE_0_2HZ 0x1 +#define ADXL345_RATE_0_39HZ 0x2 +#define ADXL345_RATE_0_78HZ 0x3 +#define ADXL345_RATE_1_56HZ 0x4 +#define ADXL345_RATE_3_13HZ 0x5 +#define ADXL345_RATE_6_25HZ 0x6 +#define ADXL345_RATE_12_5HZ 0x7 +#define ADXL345_RATE_25HZ 0x8 +#define ADXL345_RATE_50HZ 0x9 +#define ADXL345_RATE_100HZ 0xA +#define ADXL345_RATE_200HZ 0xB +#define ADXL345_RATE_400HZ 0xC +#define ADXL345_RATE_800HZ 0xD +#define ADXL345_RATE_1600HZ 0xE +#define ADXL345_RATE_3200HZ 0xF + +#define ADXL345_POWER_CTL_REG 0x2D // Power control register +#define ADXL345_POWER_CTL_STANDBY 0x00 +#define ADXL345_POWER_CTL_MEASURE 0x08 + +#define ADXL345_DATA_FORMAT_REG 0x31 // Data format register +#define ADXL345_DATA_SELFTEST_BIT 0x80 +#define ADXL345_DATA_3WIRE_BIT 0x40 +#define ADXL345_DATA_FULLRES_BIT 0x08 +#define ADXL345_DATA_MSB_BIT 0x04 +#define ADXL345_DATA_RANGE_MASK 3 +#define ADXL345_DATA_RANGE_2G 0 +#define ADXL345_DATA_RANGE_4G 1 +#define ADXL345_DATA_RANGE_8G 2 +#define ADXL345_DATA_RANGE_16G 3 + +#define ADXL345_DATA_X0_REG 0x32 // Measurement data registers +#define ADXL345_DATA_X1_REG 0x33 +#define ADXL345_DATA_Y0_REG 0x34 +#define ADXL345_DATA_Y1_REG 0x35 +#define ADXL345_DATA_Z0_REG 0x36 +#define ADXL345_DATA_Z1_REG 0x37 + +#define ADXL345_FIFO_CTL_REG 0x38 // FIFO control register +#define ADXL345_FIFO_CTL_MODE_BYPASS 0x00 +#define ADXL345_FIFO_CTL_MODE_ENABLED 0x40 +#define ADXL345_FIFO_CTL_MODE_STREAM 0x80 +#define ADXL345_FIFO_CTL_MODE_TRIGGER 0xC0 +#define ADXL345_FIFO_CTL_SAMPLE_COUNT_MASK 0x1F + +#define ADXL345_FIFO_STATUS_REG 0x39 // FIFO status register +#define ADXL345_FIFO_STATUS_ENTRIES_MASK 0x3F + +struct ADXL345_measurement_t { + int16_t x; + int16_t y; + int16_t z; +}; + +class ADXL345 { + public: + void begin(); + void end(); + void readMeasurement(ADXL345_measurement_t * acceleration); + void writeRegister(uint8_t registerAddress, uint8_t data); + void readRegister(uint8_t registerAddress, int numBytes, uint8_t * buffer); + private: + void select(const bool select); +}; + +extern ADXL345 adxl345; \ No newline at end of file diff --git a/ini/features.ini b/ini/features.ini index b2e9d6884e75..4d6ad7b55641 100644 --- a/ini/features.ini +++ b/ini/features.ini @@ -12,6 +12,7 @@ # The order of the features matters for source-filter resolution inside of common-dependencies.py. [features] +HAS_ADXL345_ACCELEROMETER = build_src_filter=+ YHCB2004 = LiquidCrystal_AIP31068=https://github.com/ellensp/LiquidCrystal_AIP31068/archive/3fc43b7.zip, red-scorp/SoftSPIB@^1.1.1 HAS_TFT_LVGL_UI = lvgl=https://github.com/makerbase-mks/LVGL-6.1.1-MKS/archive/a3ebe98bc6.zip build_src_filter=+ From d7e679ddf93b2bb5736d410901978333e801cb20 Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Tue, 24 Dec 2024 02:28:01 +0800 Subject: [PATCH 13/21] Cleanup, improve FDCAN bus timing/sampling accuracy General cleanup FDCAN bit sampling point moved to recommended 87.5% --- Marlin/src/HAL/STM32/CAN.cpp | 2 +- Marlin/src/HAL/STM32/FDCAN.cpp | 243 +++++++++++++++++---------------- 2 files changed, 123 insertions(+), 122 deletions(-) diff --git a/Marlin/src/HAL/STM32/CAN.cpp b/Marlin/src/HAL/STM32/CAN.cpp index 27c3bb85d7ae..6cc9069d7595 100644 --- a/Marlin/src/HAL/STM32/CAN.cpp +++ b/Marlin/src/HAL/STM32/CAN.cpp @@ -590,7 +590,7 @@ void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle) { // Called by HAL_CAN_Init Error_Handler(); // CAN1 clock enable - if (__HAL_RCC_CAN1_IS_CLK_DISABLED()); // Enable CAN1 clock + if (__HAL_RCC_CAN1_IS_CLK_DISABLED()) __HAL_RCC_CAN1_CLK_ENABLE(); // Enable CAN1 clock if (__HAL_RCC_GPIOB_IS_CLK_DISABLED()) // Should be enabled by Marlin already diff --git a/Marlin/src/HAL/STM32/FDCAN.cpp b/Marlin/src/HAL/STM32/FDCAN.cpp index de066696faa8..395b1c8694c4 100644 --- a/Marlin/src/HAL/STM32/FDCAN.cpp +++ b/Marlin/src/HAL/STM32/FDCAN.cpp @@ -22,7 +22,7 @@ #include "../../inc/MarlinConfigPre.h" -#if ENABLED(CAN_TOOLHEAD) +#if ENABLED(FDCAN_TOOLHEAD) #include "../platforms.h" #include "../../gcode/gcode.h" @@ -34,23 +34,22 @@ #include "../../feature/controllerfan.h" #include "../../core/serial.h" -#include "../SHARED/FDCAN.h" - -#define CAN_DEBUG // Enable to show CAN debug messages +#include "../shared/FDCAN.h" #define FDCAN_RX_FIFO0_MASK (FDCAN_IR_RF0L | FDCAN_IR_RF0F | FDCAN_IR_RF0N) // Fifo 0: Message lost | Fifo full | New message #define FDCAN_RX_FIFO1_MASK (FDCAN_IR_RF1L | FDCAN_IR_RF1F | FDCAN_IR_RF1N) // Fifo 1: Message lost | Fifo full | New message #define FDCAN_RX_FIFO_MASK (FDCAN_RX_FIFO0_MASK | FDCAN_RX_FIFO1_MASK) // Fifo : Message lost | Fifo full | New message #define HAL_TIM_FLAG_ALL (TIM_FLAG_CC1 | TIM_FLAG_CC2 | TIM_FLAG_CC3 | TIM_FLAG_CC4 | TIM_FLAG_UPDATE | TIM_FLAG_BREAK | TIM_FLAG_BREAK2 | TIM_FLAG_TRIGGER | TIM_FLAG_COM) -#define CAN_TIMESTAMP_NO 7777 +#define FDCAN_MAX_WAIT_TIME 25 // Amount of ms to wait for for resources (buffers/command buffers) +#define CAN_TIMESTAMP_NO 7777 // M7777 is used to handle timesync message // IMPORTANT NOTE // =============== // 1. In \Users\\.platformio\packages\framework-arduinoststm32@4.20600.231001\libraries\SrcWrapper\src\HardwareTimer.cpp // Add function "__weak" in front of "void TIM16_IRQHandler(void)" -FDCAN_HandleTypeDef hfdcan2; +FDCAN_HandleTypeDef hfdcan; bool Head_Not_configured = true; // Check if the configuration was send to the head char gcode_type[4] = { 'D', 'G', 'M', 'D' }; // This way an extended ID is always > 2048 @@ -128,14 +127,14 @@ HAL_StatusTypeDef CAN_Receive(uint32_t Fifo) { // ISR! Process received CAN mess FDCAN_RxHeaderTypeDef CanRxHeader; // Receive CAN message buffer - status = HAL_FDCAN_GetRxMessage(&hfdcan2, Fifo, &CanRxHeader, can_rxbuf); + status = HAL_FDCAN_GetRxMessage(&hfdcan, Fifo, &CanRxHeader, can_rxbuf); if (status != HAL_OK) return status; uint32_t identifier = CanRxHeader.Identifier; if (CanRxHeader.IdType == FDCAN_EXTENDED_ID) { // Receiving new gcode - if (((identifier >> IDENTIFIER_GCODE_NUMBER_OFFSET) & IDENTIFIER_GCODE_MASK) == ((GCODE_TYPE_M << (IDENTIFIER_GCODE_TYPE_OFFSET - IDENTIFIER_GCODE_NUMBER_OFFSET)) + CAN_TIMESTAMP_NO)) { // TIME SYNC RESPONSE MESSAGE + if (((identifier >> IDENTIFIER_GCODE_NUMBER_OFFSET) & IDENTIFIER_GCODE_MASK) == ((GCODE_TYPE_M << (IDENTIFIER_GCODE_TYPE_OFFSET - IDENTIFIER_GCODE_NUMBER_OFFSET)) + CAN_TIMESTAMP_NO)) { // Time sync response message uint32_t *uint32p = (uint32_t *)can_rxbuf; t[3] = micros(); // Record time sync response message receive time t[1] = *uint32p++; @@ -144,7 +143,7 @@ HAL_StatusTypeDef CAN_Receive(uint32_t Fifo) { // ISR! Process received CAN mess } // Check for M997 reset command - if (((identifier >> IDENTIFIER_GCODE_NUMBER_OFFSET) & IDENTIFIER_GCODE_MASK) == ((GCODE_TYPE_M << (IDENTIFIER_GCODE_TYPE_OFFSET - IDENTIFIER_GCODE_NUMBER_OFFSET)) + 997)) { // CODE IS M997, RESTART HEAD UNCONDITIONALLY + if (((identifier >> IDENTIFIER_GCODE_NUMBER_OFFSET) & IDENTIFIER_GCODE_MASK) == ((GCODE_TYPE_M << (IDENTIFIER_GCODE_TYPE_OFFSET - IDENTIFIER_GCODE_NUMBER_OFFSET)) + 997)) { // Gcode is M997, Restart the toolhead unconditionally flashFirmware(0); while (1); } @@ -182,20 +181,19 @@ HAL_StatusTypeDef CAN_Receive(uint32_t Fifo) { // ISR! Process received CAN mess } } - if (parameter_counter == 0) { // Gcode is complete, process the gcode + if (parameter_counter == 0) { // Gcode is complete, including all parameters, process the gcode gcode_counter++; uint32_t ms = millis(); bool queue_status; - do { // BLOCKING! Wait for free Marlin command buffer for max 50ms - queue_status = queue.enqueue_one(can_gcode_buffer); + do { + queue_status = queue.enqueue_one(can_gcode_buffer); // TRUE if the command was queued, FALSE if buffer is full } - while (!queue_status && (millis() - ms < 50)); + while (!queue_status && (millis() - ms < FDCAN_MAX_WAIT_TIME)); // BLOCKING! Wait for free Marlin command buffer if (!queue_status) // Could not store the received CAN command in Marlin command buffer CAN_Error_Code |= CAN_ERROR_MARLIN_CMD_BUFFER_OVERFLOW; } - return HAL_OK; } @@ -215,24 +213,30 @@ void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs) __HAL_FDCAN_CLEAR_FLAG(hfdcan, FDCAN_IT_RX_FIFO1_MESSAGE_LOST); // Clear interrupt flag } -if (RxFifo1ITs & FDCAN_IT_RX_FIFO1_NEW_MESSAGE) - CAN_Receive(FDCAN_RX_FIFO1); + if (RxFifo1ITs & FDCAN_IT_RX_FIFO1_NEW_MESSAGE) + CAN_Receive(FDCAN_RX_FIFO1); } -void TIM16_IRQHandler(void) { // FDCAN INTERRUPT HANDLER, OVERRIDE WEAK FUNCTION +void TIM16_IRQHandler(void) { // ISR! FDCAN INTERRUPT HANDLER, OVERRIDE WEAK FUNCTION if ((HardwareTimer_Handle[TIMER16_INDEX]) && (HardwareTimer_Handle[TIMER16_INDEX]->handle.Instance->SR & HAL_TIM_FLAG_ALL)) // Interrupt was caused by timer HAL_TIM_IRQHandler(&HardwareTimer_Handle[TIMER16_INDEX]->handle); // Forward to timer interrupt handler - uint32_t RxFifoITs = hfdcan2.Instance->IR & FDCAN_RX_FIFO_MASK; - RxFifoITs &= hfdcan2.Instance->IE; + uint32_t RxFifoITs = hfdcan.Instance->IR & FDCAN_RX_FIFO_MASK; + RxFifoITs &= hfdcan.Instance->IE; if (RxFifoITs) // Any Fifo read interrupts? - HAL_FDCAN_IRQHandler(&hfdcan2); // Forward call to FDCAN interrupt handler + HAL_FDCAN_IRQHandler(&hfdcan); // Forward call to FDCAN interrupt handler } +//void HAL_FDCAN_ErrorCallback(FDCAN_HandleTypeDef *hfdcan) { // TODO: Catch errors +//} + +//void HAL_FDCAN_ErrorStatusCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ErrorStatusITs) { // TODO: Catch errors +//} + void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* fdcanHandle) { // Called automatically, configure GPIO for FDCAN, enable interrupts - GPIO_InitTypeDef GPIO_InitStruct = {0}; - RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; + GPIO_InitTypeDef GPIO_InitStruct = { 0 }; + RCC_PeriphCLKInitTypeDef PeriphClkInit = { 0 }; PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_FDCAN; PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_PCLK1; @@ -240,21 +244,22 @@ void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* fdcanHandle) { // Called automatical if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) Error_Handler(); - // FDCAN1/2 clock enable (1 clock for both CAN devices) + // STM32G0B1 and STM32G0 C1 only + // FDCAN1/2 clock enable (one clock for both CAN devices) if (__HAL_RCC_FDCAN_IS_CLK_DISABLED()) __HAL_RCC_FDCAN_CLK_ENABLE(); if (__HAL_RCC_GPIOB_IS_CLK_DISABLED()) __HAL_RCC_GPIOB_CLK_ENABLE(); // ENABLE GPIO B CLOCK // FDCAN2 GPIO Configuration - // PB0 ------> FDCAN2_RX - // PB1 ------> FDCAN2_TX + // PB0 AF3 ------> FDCAN2_RX + // PB1 AF3 ------> FDCAN2_TX GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1; // Pin PB0 and Pin PB1 - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; - GPIO_InitStruct.Alternate = GPIO_AF3_FDCAN2; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // Alternate function + GPIO_InitStruct.Pull = GPIO_NOPULL; // No pullup or pulldown + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // High frequency device + GPIO_InitStruct.Alternate = GPIO_AF3_FDCAN2; // Alternate function 3 HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // Port B // FDCAN2 interrupt Init @@ -285,90 +290,90 @@ uint32_t TxFifoQueueMode Tx FIFO/Queue Mode selection FDCAN_TX_FIFO_OP HAL_StatusTypeDef FDCAN2_Start(void) { // START THE FDCAN BUS HAL_StatusTypeDef status = HAL_OK; - - // FDCAN baud rate = Device Clock Frequency / Clock Divider / Prescaler / SJW + TSG1 + TSG2) - // Baud rate = 64M / 1 / 4 / (1 + 10 + 5) = 1M - // Baud rate = 64M / 1 / 8 / (1 + 10 + 5) = 500k + // FDCAN baud rate = Device Clock Frequency / Clock Divider / Prescaler / SJW + TSG1 + TSG2) (SJW=Sync Jump Width) + // Baud rate = 64M / 1 / 4 / (1 + 13 + 2) = 1M (Sample Point = (1 + 13)/(1 + 13 + 2) = 87.5%) + // Baud rate = 64M / 1 / 8 / (1 + 13 + 2) = 500k (Sample Point = (1 + 13)/(1 + 13 + 2) = 87.5%) // http://www.bittiming.can-wiki.info // https://www.kvaser.com/support/calculators/can-fd-bit-timing-calculator - hfdcan2.Instance = FDCAN2; // FDCAN2 device - hfdcan2.Init.ClockDivider = FDCAN_CLOCK_DIV1; // Clock divider 1 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 - hfdcan2.Init.FrameFormat = FDCAN_FRAME_CLASSIC; // FDCAN_FRAME_CLASSIC / FDCAN_FRAME_FD_BRS / FDCAN_FRAME_FD_NO_BRS (Bit Rate Switching) - hfdcan2.Init.Mode = FDCAN_MODE_NORMAL; // FDCAN_MODE_NORMAL / FDCAN_MODE_EXTERNAL_LOOPBACK / FDCAN_MODE_INTERNAL_LOOPBACK / FDCAN_MODE_BUS_MONITORING / FDCAN_MODE_RESTRICTED_OPERATION - hfdcan2.Init.AutoRetransmission = DISABLE; // Auto Retransmission of message if error occured, can cause lockup - hfdcan2.Init.TransmitPause = DISABLE; // Transmit Pause to allow for other device to send message - hfdcan2.Init.ProtocolException = DISABLE; // ProtocolException - - hfdcan2.Init.NominalPrescaler = hfdcan2.Init.DataPrescaler = 4; // Arbitration/data clock prescaler (classic only) - hfdcan2.Init.NominalSyncJumpWidth = hfdcan2.Init.DataSyncJumpWidth = 1; // Arbitration/data sync jump width - hfdcan2.Init.NominalTimeSeg1 = hfdcan2.Init.DataTimeSeg1 = 10; // Arbitration/data period 1 - hfdcan2.Init.NominalTimeSeg2 = hfdcan2.Init.DataTimeSeg2 = 5; // Arbitration period 2 - - hfdcan2.Init.StdFiltersNbr = 2; // Number of standard frame filters - hfdcan2.Init.ExtFiltersNbr = 2; // Number of extended frame filters - hfdcan2.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; // Queue mode: FDCAN_TX_FIFO_OPERATION / FDCAN_TX_QUEUE_OPERATION - - status = HAL_FDCAN_Init(&hfdcan2); + hfdcan.Instance = FDCAN2; // FDCAN2 device + hfdcan.Init.ClockDivider = FDCAN_CLOCK_DIV1; // Clock divider 1 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 + hfdcan.Init.FrameFormat = FDCAN_FRAME_CLASSIC; // FDCAN_FRAME_CLASSIC / FDCAN_FRAME_FD_BRS / FDCAN_FRAME_FD_NO_BRS (Bit Rate Switching) + hfdcan.Init.Mode = FDCAN_MODE_NORMAL; // FDCAN_MODE_NORMAL / FDCAN_MODE_EXTERNAL_LOOPBACK / FDCAN_MODE_INTERNAL_LOOPBACK / FDCAN_MODE_BUS_MONITORING / FDCAN_MODE_RESTRICTED_OPERATION + hfdcan.Init.AutoRetransmission = DISABLE; // Auto Retransmission of message if error occured, can cause lockup + hfdcan.Init.TransmitPause = DISABLE; // Transmit Pause to allow for other device to send message + hfdcan.Init.ProtocolException = DISABLE; // ProtocolException + + hfdcan.Init.NominalPrescaler = hfdcan.Init.DataPrescaler = 4; // Arbitration/data clock prescaler (1-16) + hfdcan.Init.NominalSyncJumpWidth = hfdcan.Init.DataSyncJumpWidth = 1; // Arbitration/data sync jump width (1-16) + hfdcan.Init.NominalTimeSeg1 = hfdcan.Init.DataTimeSeg1 = 13; // Arbitration/data period 1 (1-32) + hfdcan.Init.NominalTimeSeg2 = hfdcan.Init.DataTimeSeg2 = 2; // Arbitration/data period 2 (1-16) + + hfdcan.Init.StdFiltersNbr = 2; // Number of standard frame filters + hfdcan.Init.ExtFiltersNbr = 2; // Number of extended frame filters + hfdcan.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; // Queue mode: FDCAN_TX_FIFO_OPERATION / FDCAN_TX_QUEUE_OPERATION + + status = HAL_FDCAN_Init(&hfdcan); if (status != HAL_OK) return status; - FDCAN_FilterTypeDef sFilterConfig; // Configure RX message filter - - sFilterConfig.IdType = FDCAN_EXTENDED_ID; // Filter to FIFO1 if higest ID bit is set - sFilterConfig.FilterIndex = 0; // Exteneded filter ID 0 - sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE + FDCAN_FilterTypeDef sFilterConfig; // Configure RX message filter + sFilterConfig.IdType = FDCAN_EXTENDED_ID; // Filter to FIFO1 if higest ID bit is set + sFilterConfig.FilterIndex = 0; // Exteneded filter ID 0 + sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE + sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1; // Filter to FIFO1 + sFilterConfig.FilterID1 = 0x10000000; // Range: 0 - 0x1FFF FFFF, 0=don'tcare + sFilterConfig.FilterID2 = 0x10000000; // Range: 0 - 0x1FFF FFFF, 0=don'tcare + HAL_FDCAN_ConfigFilter(&hfdcan, &sFilterConfig); + + sFilterConfig.IdType = FDCAN_EXTENDED_ID; // Filter all remaining messages to FIFO0 + sFilterConfig.FilterIndex = 1; // Exteneded filter ID 1 + sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE + sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; // Remaining messages go to FIFO0 + sFilterConfig.FilterID1 = 0; // Range: 0 - 0x1FFF FFFF, 0=don'tcare + sFilterConfig.FilterID2 = 0; // Range: 0 - 0x1FFF FFFF, 0=don'tcare + HAL_FDCAN_ConfigFilter(&hfdcan, &sFilterConfig); + + sFilterConfig.IdType = FDCAN_STANDARD_ID; // Filter to FIFO1 if higest ID bit is set + sFilterConfig.FilterIndex = 0; // Standard filter ID 0 + sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1; // Filter to FIFO1 - sFilterConfig.FilterID1 = 0x10000000; // Range: 0 - 0x1FFF FFFF, 0=don'tcare - sFilterConfig.FilterID2 = 0x10000000; // Range: 0 - 0x1FFF FFFF, 0=don'tcare - HAL_FDCAN_ConfigFilter(&hfdcan2, &sFilterConfig); - - sFilterConfig.IdType = FDCAN_EXTENDED_ID; // Filter all remaining messages to FIFO0 - sFilterConfig.FilterIndex = 1; // Exteneded filter ID 1 - sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE - sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; // Remaining messages go to FIFO1 - sFilterConfig.FilterID1 = 0; // Range: 0 - 0x1FFF FFFF, 0=don'tcare - sFilterConfig.FilterID2 = 0; // Range: 0 - 0x1FFF FFFF, 0=don'tcare - HAL_FDCAN_ConfigFilter(&hfdcan2, &sFilterConfig); - - sFilterConfig.IdType = FDCAN_STANDARD_ID; // Filter to FIFO1 if higest ID bit is set - sFilterConfig.FilterIndex = 0; // Standard filter ID 0 - sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE - sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1; - sFilterConfig.FilterID1 = 0b10000000000; // Range: 0 - 0x7FF, 0=don't care - sFilterConfig.FilterID2 = 0b10000000000; // Range: 0 - 0x7FF, 0=don't care - HAL_FDCAN_ConfigFilter(&hfdcan2, &sFilterConfig); - - sFilterConfig.IdType = FDCAN_STANDARD_ID; // Filter all remaining messages to FIFO0 - sFilterConfig.FilterIndex = 1; // Standard filter ID 1 - sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE - sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; // Remaining to FIFO1 - sFilterConfig.FilterID1 = 0; // Range: 0 - 0x7FF, 0=don't care - sFilterConfig.FilterID2 = 0; // Range: 0 - 0x7FF, 0=don't care - HAL_FDCAN_ConfigFilter(&hfdcan2, &sFilterConfig); - -// Configure global filter -// status = HAL_FDCAN_ConfigGlobalFilter(&hfdcan2, FDCAN_REJECT, FDCAN_REJECT, FDCAN_REJECT_REMOTE, FDCAN_REJECT_REMOTE); + sFilterConfig.FilterID1 = 0b10000000000; // Range: 0 - 0x7FF, 0=don't care + sFilterConfig.FilterID2 = 0b10000000000; // Range: 0 - 0x7FF, 0=don't care + HAL_FDCAN_ConfigFilter(&hfdcan, &sFilterConfig); + + sFilterConfig.IdType = FDCAN_STANDARD_ID; // Filter all remaining messages to FIFO0 + sFilterConfig.FilterIndex = 1; // Standard filter ID 1 + sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE + sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; // Remaining messages go to FIFO0 + sFilterConfig.FilterID1 = 0; // Range: 0 - 0x7FF, 0=don't care + sFilterConfig.FilterID2 = 0; // Range: 0 - 0x7FF, 0=don't care + HAL_FDCAN_ConfigFilter(&hfdcan, &sFilterConfig); // Start the FDCAN module - status = HAL_FDCAN_Start(&hfdcan2); // ===== START FDCAN2 DEVICE ===== + status = HAL_FDCAN_Start(&hfdcan); // Activate RX FIFO0 new message interrupt if (status == HAL_OK) - status = HAL_FDCAN_ActivateNotification(&hfdcan2, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0); // Calls TIM16_IRQHandler + status = HAL_FDCAN_ActivateNotification(&hfdcan, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0); // Calls TIM16_IRQHandler (STM32G0xx) // Activate RX FIFO0 message lost interrupt if (status == HAL_OK) - status = HAL_FDCAN_ActivateNotification(&hfdcan2, FDCAN_IT_RX_FIFO0_MESSAGE_LOST, 0); // Calls TIM16_IRQHandler + status = HAL_FDCAN_ActivateNotification(&hfdcan, FDCAN_IT_RX_FIFO0_MESSAGE_LOST, 0); // Calls TIM16_IRQHandler (STM32G0xx) // Activate RX FIFO1 new message interrupt if (status == HAL_OK) - status = HAL_FDCAN_ActivateNotification(&hfdcan2, FDCAN_IT_RX_FIFO1_NEW_MESSAGE, 0); // Calls TIM16_IRQHandler + status = HAL_FDCAN_ActivateNotification(&hfdcan, FDCAN_IT_RX_FIFO1_NEW_MESSAGE, 0); // Calls TIM16_IRQHandler (STM32G0xx) // Activate RX FIFO1 message lost interrupt if (status == HAL_OK) - status = HAL_FDCAN_ActivateNotification(&hfdcan2, FDCAN_IT_RX_FIFO1_MESSAGE_LOST, 0); // Calls TIM16_IRQHandler - - return status; + status = HAL_FDCAN_ActivateNotification(&hfdcan, FDCAN_IT_RX_FIFO1_MESSAGE_LOST, 0); // Calls TIM16_IRQHandler (STM32G0xx) + + // TODO: Handle/Catch errors + // if (status == HAL_OK) + // status = HAL_FDCAN_ActivateNotification(&hfdcan, FDCAN_IT_ERROR_PASSIVE, 0); // Calls TIM16_IRQHandler (STM32G0xx) + // FDCAN_IT_LIST_PROTOCOL_ERROR + + return status; } /* ---INTERRUPT HANDLERS------------------------------------------------------------- @@ -416,24 +421,24 @@ HAL_StatusTypeDef CAN_Send_Message(bool TempUpdate) { // Called from temperature else CanTxHeader.DataLength = FDCAN_DLC_BYTES_0; // 0 data byte CAN message bool request_time_sync = CAN_request_time_sync; - uint32_t VirtualIO = (Head_Not_configured << CAN_REQUEST_SETUP_BIT) | // Report Head is not configured - (READ(Z_MIN_PROBE_PIN) << CAN_PROBE_BIT) | // Report probe status - (READ(FILAMENT_RUNOUT_PIN) << CAN_FILAMENT_BIT) | // Report filament detector status - (request_time_sync << CAN_REQUEST_TIME_SYNC_BIT) | - ((!!CAN_Error_Code) << CAN_ERROR_BIT); // Report error (if any) - // IRON, ADD PINS YOU WANT TO MONITOR HERE + uint32_t VirtualIO = (Head_Not_configured << CAN_REQUEST_SETUP_BIT) | // Report Head is not configured + (READ(Z_MIN_PROBE_PIN) << CAN_PROBE_BIT) | // Report probe status + (READ(FILAMENT_RUNOUT_PIN) << CAN_FILAMENT_BIT) | // Report filament detector status + (request_time_sync << CAN_REQUEST_TIME_SYNC_BIT) | // Report timesync request + ((!!CAN_Error_Code) << CAN_ERROR_BIT); // Report error (if any) + // ADD PINS YOU WANT TO MONITOR HERE CanTxHeader.Identifier = ((CanTxHeader.Identifier ^ STDID_FIFO_BIT) & STDID_FIFO_BIT) + VirtualIO; // Toggle FIFO bit for receiver filtering uint32_t ms = millis(); - while ((HAL_FDCAN_GetTxFifoFreeLevel(&hfdcan2) == 0) && (millis() - ms < 25)) { } // BLOCKING! Wait max 50ms for free TX FIFO slot + while ((HAL_FDCAN_GetTxFifoFreeLevel(&hfdcan) == 0) && (millis() - ms < FDCAN_MAX_WAIT_TIME)) { } // BLOCKING! Wait for free TX FIFO slot if (request_time_sync) { // For time sync, wait until all message TX slots are empty before sending CAN_request_time_sync = false; ms = millis(); - while ((HAL_FDCAN_GetTxFifoFreeLevel(&hfdcan2) < 3) && (millis() - ms < 25)) { } // BLOCKING! Wait max 50ms for free TX FIFO slot + while ((HAL_FDCAN_GetTxFifoFreeLevel(&hfdcan) < 3) && (millis() - ms < FDCAN_MAX_WAIT_TIME)) { } // BLOCKING! Wait for free TX FIFO slot t[0] = micros(); // Store time sync message send time } - status = HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan2, &CanTxHeader, can_tx_buffer); // Send FDCAN message + status = HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan, &CanTxHeader, can_tx_buffer); // Send FDCAN message CAN_message_counter++; return status; } @@ -455,9 +460,7 @@ HAL_StatusTypeDef CAN_Send_String(const char * message) { // Send string message int remaining = strlen(message); // Length of string int index = 0; - #ifdef CAN_DEBUG - SERIAL_ECHOPGM(">>> SENDING STRING MESSAGE TO HOST: ", message); // IRON, SHOULD HAVE '\n' AT THE END, DEBUGGING - #endif + SERIAL_ECHOPGM(">>> SENDING STRING MESSAGE TO HOST: ", message); // IRON, SHOULD HAVE '\n' AT THE END, DEBUGGING while (remaining > 0) { // Keep sending message if there are more characters to send int c = MIN(8, remaining); // Max 8 character at a time @@ -474,9 +477,9 @@ HAL_StatusTypeDef CAN_Send_String(const char * message) { // Send string message CanTxHeader.Identifier = ((CanTxHeader.Identifier ^ 0b10000000000) & 0b10000000000) + VirtualIO; // Toggle FIFO bit for receiver filtering uint32_t ms = millis(); - while ((HAL_FDCAN_GetTxFifoFreeLevel(&hfdcan2) < 2) && (millis() - ms < 50)) { } // BLOCKING! Wait for free TX FIFO slot, with 1 spare slot for urgent messages + while ((HAL_FDCAN_GetTxFifoFreeLevel(&hfdcan) < 2) && (millis() - ms < FDCAN_MAX_WAIT_TIME)) { } // BLOCKING! Wait for free TX FIFO slot, with 1 spare slot for urgent messages - status = HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan2, &CanTxHeader, can_tx_buffer); // Send message + status = HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan, &CanTxHeader, can_tx_buffer); // Send message CAN_message_counter++; } @@ -488,8 +491,6 @@ uint32_t last_error_message = 0; uint32_t oldExtuder_auto_fan_speed = controllerFan.settings.extruder_auto_fan_speed; void FDCAN_idle() { // Called from MarlinCore.cpp -// heart beat signal, send and receive -// error monitoring (hotend auto fan speed) if (oldExtuder_auto_fan_speed != controllerFan.settings.extruder_auto_fan_speed) { controllerFan.settings.extruder_auto_fan_speed = MAX(controllerFan.settings.extruder_auto_fan_speed, PROBING_AUTO_FAN_SPEED); // Should not be required, safeguard! @@ -537,11 +538,11 @@ void FDCAN_idle() { // Called from MarlinCore.cpp t[3] = 0; } - if (hfdcan2.ErrorCode) { + if (hfdcan.ErrorCode) { uint32_t ms = millis(); if ((ms - last_error_message) > 20000) { // 20 seconds repeat - MString<40> buffer(F("Error: fdcan2.ErrorCode="), hfdcan2.ErrorCode, '\n'); + MString<40> buffer(F("Error: fdcan2.ErrorCode="), hfdcan.ErrorCode, '\n'); CAN_Send_String(buffer); last_error_message = ms; } @@ -558,19 +559,19 @@ void FDCAN_idle() { // Called from MarlinCore.cpp if (CAN_Error_Code != Old_Error_Code) { // Show message on new error code switch(CAN_Error_Code) { case CAN_ERROR_FIFO_OVERFLOW: - CAN_Send_String("Error: HEAD CAN FIFO OVERFLOW\n"); - SERIAL_ECHOLNPGM(">>> ERROR: HEAD CAN FIFO OVERFLOW"); - break; + CAN_Send_String("Error: TOOLHEAD CAN FIFO OVERFLOW\n"); + SERIAL_ECHOLNPGM(">>> ERROR: TOOLHEAD CAN FIFO OVERFLOW"); + break; case CAN_ERROR_INCOMPLETE_GCODE_RECEIVED: - CAN_Send_String("Error: HEAD INCOMPLETE GCODE MESSAGE\n"); - SERIAL_ECHOLNPGM(">>> ERROR: HEAD INCOMPLETE GCODE MESSAGE"); - break; + CAN_Send_String("Error: TOOLHEAD INCOMPLETE GCODE MESSAGE\n"); + SERIAL_ECHOLNPGM(">>> ERROR: TOOLHEAD INCOMPLETE GCODE MESSAGE"); + break; case CAN_ERROR_MARLIN_CMD_BUFFER_OVERFLOW: - CAN_Send_String("Error: HEAD MARLIN CMD BUF OVERFLOW\n"); - SERIAL_ECHOLNPGM(">>> ERROR: HEAD MARLIN CMD BUF OVERFLOW"); - break; + CAN_Send_String("Error: TOOLHEAD MARLIN CMD BUF OVERFLOW\n"); + SERIAL_ECHOLNPGM(">>> ERROR: TOOLHEAD MARLIN CMD BUF OVERFLOW"); + break; default: { } } // Switch @@ -590,4 +591,4 @@ HAL_StatusTypeDef HAL_FDCAN_TT_DeactivateNotification(FDCAN_HandleTypeDef *hfdca void HAL_FDCAN_IRQHandler(FDCAN_HandleTypeDef *hfdcan); */ -#endif // CAN_TOOLHEAD \ No newline at end of file +#endif // FDCAN_TOOLHEAD \ No newline at end of file From 7da4c1328a7c1033216e6dfc148a386b18afe37e Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Fri, 10 Jan 2025 22:44:55 +0800 Subject: [PATCH 14/21] Added FDCAN host for STM32H7, wear leveling for STM32H7 Various performance improvements on host and toolhead side --- Marlin/Configuration.h | 3 +- Marlin/src/HAL/STM32/CAN.cpp | 782 ---------------- Marlin/src/HAL/STM32/CAN_host.cpp | 780 ++++++++++++++++ Marlin/src/HAL/STM32/FDCAN.cpp | 594 ------------ Marlin/src/HAL/STM32/FDCAN_host.cpp | 867 ++++++++++++++++++ Marlin/src/HAL/STM32/FDCAN_toolhead.cpp | 732 +++++++++++++++ Marlin/src/HAL/STM32/HardwareSerial.cpp | 292 ++++-- Marlin/src/HAL/STM32/HardwareSerial.h | 6 +- Marlin/src/HAL/STM32/Servo.cpp | 12 +- Marlin/src/HAL/STM32/eeprom_flash.cpp | 33 +- Marlin/src/HAL/STM32/inc/SanityCheck.h | 4 +- Marlin/src/HAL/shared/CAN.h | 50 - Marlin/src/HAL/shared/CAN_host.h | 108 +++ Marlin/src/HAL/shared/FDCAN.h | 48 - Marlin/src/MarlinCore.cpp | 35 +- Marlin/src/feature/runout.h | 10 +- Marlin/src/gcode/gcode.cpp | 10 +- Marlin/src/gcode/host/M115.cpp | 12 + Marlin/src/gcode/temp/M306.cpp | 24 +- Marlin/src/inc/SanityCheck.h | 2 +- Marlin/src/module/endstops.cpp | 13 +- Marlin/src/module/probe.h | 8 +- Marlin/src/module/settings.cpp | 4 + Marlin/src/module/temperature.cpp | 19 +- .../pins/stm32f4/pins_MKS_MONSTER8_common.h | 2 +- 25 files changed, 2793 insertions(+), 1657 deletions(-) delete mode 100644 Marlin/src/HAL/STM32/CAN.cpp create mode 100644 Marlin/src/HAL/STM32/CAN_host.cpp delete mode 100644 Marlin/src/HAL/STM32/FDCAN.cpp create mode 100644 Marlin/src/HAL/STM32/FDCAN_host.cpp create mode 100644 Marlin/src/HAL/STM32/FDCAN_toolhead.cpp delete mode 100644 Marlin/src/HAL/shared/CAN.h create mode 100644 Marlin/src/HAL/shared/CAN_host.h delete mode 100644 Marlin/src/HAL/shared/FDCAN.h diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index e315634fbb30..1c78602d99e3 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -125,8 +125,9 @@ #endif // Enable CAN bus support and protocol -//#define CAN_MASTER +//#define CAN_HOST //#define CAN_TOOLHEAD +//#define CAN_DEBUG // Enable the Bluetooth serial interface on AT90USB devices //#define BLUETOOTH diff --git a/Marlin/src/HAL/STM32/CAN.cpp b/Marlin/src/HAL/STM32/CAN.cpp deleted file mode 100644 index 6cc9069d7595..000000000000 --- a/Marlin/src/HAL/STM32/CAN.cpp +++ /dev/null @@ -1,782 +0,0 @@ -/** - * 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 . - * - */ - -/** - * Contributor Notes: - * NOTE 1: For MKS Monster 8 V1/V2 on Arduino use: Board "Generic STM32F4 series", Board part number "Generic F407VETx" - * NOTE 2: Requires `HAL_CAN_MODULE_ENABLED`, e.g., with `-DHAL_CAN_MODULE_ENABLED` - * For Arduino IDE use "hal_conf_extra.h" with `#define HAL_CAN_MODULE_ENABLED` - * NOTE 3: To accept all CAN messages, enable 1 filter (FilterBank = 0) in "FilterMode = CAN_FILTERMODE_IDMASK", mask and ID = 0 (0=don't care) - * NOTE 4: Serial communication in ISR causes issues! Hangs etc. so avoid this! - * NOTE 5: A FIFO storage cell is called a "Mailbox" in STM32F4xx, FIFO0 and FiFO1 can hold 3 CAN messages each. - * NOTE 6: The filter ID/mask numbers (LOW/HIGH) do not directly relate to the message ID numbers (See Figure 342 in RM0090) - */ - -#include "../../inc/MarlinConfigPre.h" - -#if ENABLED(CAN_MASTER) - -#include "../platforms.h" -#include "../../gcode/parser.h" -#include "../../module/temperature.h" -#include "../../module/motion.h" // For current_position variable -#include "../../module/planner.h" // For steps/mm parameters variables -#include "../../feature/tmc_util.h" -#include "../../module/endstops.h" -#include "../../feature/controllerfan.h" // For controllerFan settings -#include "../../libs/numtostr.h" // For float to string conversion - -#define CAN_EXTENDED_ID CAN_ID_EXT -#define CAN_STANDARD_ID CAN_ID_STD - -#define STDID_FIFO_BIT 0b10000000000 -#define EXTID_FIFO_BIT 0x10000000 - -#define CAN_IO_MASK 0b11111 // Masks for the 5 virtual IO bits (see below) -#define GCODE_NUMBER_MASK 0b1111111111111 -#define PARAMETER_MASK 0b11111 -#define PARAMETER_COUNT_MASK 0b111 -#define GCODE_TYPE_MASK 0b11 -#define CAN_PROBE_MASK 1 // Virtual IO bit for probe -#define CAN_FILAMENT_MASK 2 // Virtual IO bit for filament -#define CAN_X_ENDSTOP_MASK 4 // Virtual IO bit for X-endstop -#define CAN_Y_ENDSTOP_MASK 8 // Virtual IO bit for Y-endstop -#define CAN_Z_ENDSTOP_MASK 16 // Virtual IO bit for Z-endstop -#define CAN_STRING_MESSAGE_MASK 32 // Signals the head sent a string message -#define CAN_REQUEST_SETUP_MASK 64 // Signals the head requests setup information -#define CAN_TMC_OT_MASK 128 // Signals the head signals a TMC Over Temp error - #define CAN_REQUEST_TIME_SYNC_MASK 256 // Signals E0 or E1 -#define CAN_ERROR_MASK 512 // Signals the head encountered an error - -#define PARAMETER1_OFFSET 0 -#define PARAMETER2_OFFSET 5 -#define GCODE_NUMBER_OFFSET 10 -#define GCODE_TYPE_OFFSET 23 -#define PARAMETER_COUNT_OFFSET 25 - -#define GCODE_TYPE_D 0 -#define GCODE_TYPE_G 1 -#define GCODE_TYPE_M 2 -#define GCODE_TYPE_T 3 - -#define GCODE_TIME_SYNC_NO 7777 // Just a unique unused number M7777 - -extern "C" void CAN1_RX0_IRQHandler(); // Override weak CAN FIFO0 interrupt handler -extern "C" void CAN1_RX1_IRQHandler(); // Override weak CAN FIFO1 interrupt handler -extern "C" void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan); // Override weak CAN interrupt callback for FIFO0 -extern "C" void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan); // Override weak CAN interrupt callback for FIFO1 -extern "C" void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan); // Override weak CAN interrupt callback - -CAN_HandleTypeDef hcan1 = { 0 }; // The CAN1 handle -CAN_TxHeaderTypeDef TxHeader = { 0 }; // Header to send a CAN message -volatile uint32_t CAN_io_state = 0; // Virtual IO state variable -volatile bool CAN_head_error = 0; // Register if an error was reported by the head -volatile bool CAN_head_setup_request = false; // Signals the head requested setup information -volatile bool CAN_time_sync_request = false; // Signals the head requested a time sync -volatile uint32_t time_sync_request_time = 0; // Record the time the time sync request was received -volatile uint32_t gcode_counter = 0; // Count amount of gcodes received -volatile uint32_t HAL_CAN_error_code = 0; // Record a host CAN error message - -volatile bool FirstE0Error = true; // First CAN bus error, show warning only once -volatile bool string_message_complete = false; // Signals a complete string message was received -volatile uint32_t string_message_index = 0; // Index into the CAN string that is being received -uint32_t Next_CAN_Temp_Report = 0; // Track when the next head temperature report will be received -uint32_t Last_CAN_Error_Message = 0; // Track when the last CAN error messaget was shown -char string_message[128] = "\0"; // CAN string message buffer for incoming message, max 128 characters - -void CAN1_RX0_IRQHandler() { // CAN FIFO0 Interrupt handler overrides standard weak CAN1_RX0_IRQHandler - HAL_CAN_IRQHandler(&hcan1); // Forwarded for callbacks --> HAL_CAN_RxFifo0MsgPendingCallback/HAL_CAN_ErrorCallback - // OR - //HAL_CAN_RxFifo0MsgPendingCallback(&hcan1); // Call the required callback directly, faster but no error reporting -} - -void CAN1_RX1_IRQHandler() { // CAN FIFO1 Interrupt handler overrides standard weak CAN1_RX0_IRQHandler - HAL_CAN_IRQHandler(&hcan1); // Forwarded for callbacks --> HAL_CAN_RxFifo1MsgPendingCallback/HAL_CAN_ErrorCallback - // OR - //HAL_CAN_RxFifo1MsgPendingCallback(&hcan1); // Call the required callback directly, faster but no error reporting -} - -// Send sync timestamp -HAL_StatusTypeDef CAN_Send_Timestamp() // Request receive timestamp + request response timestamp -{ - HAL_StatusTypeDef status = HAL_OK; - uint32_t TxMailbox; // Stores which Mailbox (0-2) is used to store the Sent TX message - - TxHeader.IDE = CAN_EXTENDED_ID; - TxHeader.DLC = 8; // Send sync time t1(receive time, uint32_t) and t2(response time, uint32_t) - TxHeader.StdId = (TxHeader.StdId ^ STDID_FIFO_BIT); // Toggle FIFO bit 10, keep FIFO toggling in sync - TxHeader.ExtId = ((TxHeader.ExtId ^ EXTID_FIFO_BIT) & EXTID_FIFO_BIT) + // Toggle FIFO bit 28 - ((TxHeader.DLC >> 2) << PARAMETER_COUNT_OFFSET) + // Data bytes (4 or 8) - (GCODE_TYPE_M << GCODE_TYPE_OFFSET) + // G/M/T/D-code - ((GCODE_TIME_SYNC_NO & GCODE_NUMBER_MASK) << GCODE_NUMBER_OFFSET) + // Gcode number - ((2 & PARAMETER_MASK) << PARAMETER2_OFFSET) + // Second parameter - ((1 & PARAMETER_MASK) << PARAMETER1_OFFSET); // First parameter - uint8_t CAN_tx_buffer[8]; // 8 bytes CAN data TX buffer - uint32_t * uint32p = (uint32_t *)CAN_tx_buffer; // Point to TX buffer - *uint32p++ = time_sync_request_time; - - uint32_t ms = millis(); // Wait until the FIFO is empty - while ((HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) < 3) && (millis() - ms < 25)) { } // BLOCKING! Wait max 25ms! - - *uint32p = micros(); // Only record the response time at the last possible moment - status = HAL_CAN_AddTxMessage(&hcan1, &TxHeader, CAN_tx_buffer, &TxMailbox); // Send message - - if (status == HAL_OK) // Count sent gcode messages - gcode_counter++; - - return status; -} - -// Send specified Gcode with max 2 parameters and 2 values via CAN bus -HAL_StatusTypeDef CAN_Send_Gcode_2params(uint32_t Gcode_type, uint32_t Gcode_no, uint32_t parameter1, float value1, uint32_t parameter2, float value2) { - switch (Gcode_type) { - case 'D': - Gcode_type = GCODE_TYPE_D; - break; - - case 'G': - Gcode_type = GCODE_TYPE_G; - break; - - case 'M': - Gcode_type = GCODE_TYPE_M; - - #ifdef CAN_DEBUG - SERIAL_ECHOPGM(">>> CAN TO HEAD: M", Gcode_no); - if (parameter1) { - SERIAL_CHAR(' ', parameter1); - if (value1 == int(value1)) - SERIAL_ECHO(i16tostr3left(value1)); // Integer value - else - SERIAL_ECHO(p_float_t(value1, 4)); // Float with 4 digits - } - - if (parameter2) { - SERIAL_CHAR(' ', parameter2); - if (value2 == int(value2)) - SERIAL_ECHO(i16tostr3left(value2)); // Integer value - else - SERIAL_ECHO(p_float_t(value2, 4)); // Float with 4 digits - } - SERIAL_EOL(); - #endif // CAN_DEBUG - - break; - - case 'T': Gcode_type = GCODE_TYPE_T; - break; - - default: return HAL_ERROR; // UNKNOWN GCODE TYPE - } - - HAL_StatusTypeDef status = HAL_OK; - uint32_t TxMailbox; // Stores which Mailbox (0-2) is used to store the Sent TX message - - if (parameter1 > 31) - parameter1 -= 64; // Format 'A' = 1, 'B' = 2, etc. - - if (parameter2 > 31) - parameter2 -= 64; // Format 'A' = 1, 'B' = 2, etc. - - TxHeader.IDE = CAN_EXTENDED_ID; - TxHeader.DLC = 4 * (!!parameter1 + !!parameter2); // Amount of bytes to send (4 or 8) - TxHeader.StdId = (TxHeader.StdId ^ STDID_FIFO_BIT); // Toggle FIFO bit 10, keep FIFO toggling in sync - TxHeader.ExtId = ((TxHeader.ExtId ^ EXTID_FIFO_BIT) & EXTID_FIFO_BIT) + // Toggle FIFO bit 28 - ((TxHeader.DLC >> 2) << PARAMETER_COUNT_OFFSET) + // Data bytes (4 or 8) - (Gcode_type << GCODE_TYPE_OFFSET) + // G/M/T/D-code - ((Gcode_no & GCODE_NUMBER_MASK) << GCODE_NUMBER_OFFSET) + // Gcode number - ((parameter2 & PARAMETER_MASK) << PARAMETER2_OFFSET) + // Second parameter - ((parameter1 & PARAMETER_MASK) << PARAMETER1_OFFSET); // First parameter - uint8_t CAN_tx_buffer[8]; // 8 bytes CAN data TX buffer - float * fp = (float *)CAN_tx_buffer; // Point to TX buffer - *fp++ = value1; - *fp = value2; - - const uint32_t ms = millis(); // Don't send too fast! - while ((HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) == 0) && (millis() - ms < 25)) { } // BLOCKING! Wait max 25ms! - //SERIAL_ECHOLNPGM(">>> Waited1: ", millis() - ms, " FreeTX: ", HAL_CAN_GetTxMailboxesFreeLevel(&hcan1)); // IRON, DEBUGGING - status = HAL_CAN_AddTxMessage(&hcan1, &TxHeader, CAN_tx_buffer, &TxMailbox); // Send message - - if (status == HAL_OK) // Count sent gcode messages - gcode_counter++; - - return status; -} - -HAL_StatusTypeDef CAN_Send_Setup() { // Send setup to HEAD - CAN_head_setup_request = false; - SERIAL_ECHOLNPGM(">>> CAN: SENDING CONFIG TO HEAD ====="); - // NOTE: Sending many command too fast will cause a Marlin command buffer overrun at the head, add delays if needed - //CAN_Send_Gcode_2params('M', 104, 'S', 0, 0, 0); // M104 S0, switch off hotend heating, NOT NEEDED ANYMORE - //CAN_Send_Gcode_2params('M', 107, 0, 0, 0, 0); // M107, switch off cooling fan, NOT NEEDED ANYMORE - //CAN_Send_Gcode_2params('M', 18, 0, 0, 0, 0); // M18, switch off steppers, NOT NEEDED ANYMORE - - #if ENABLED(MPCTEMP) - // M306 MPC settings (managed by host) - MPC_t &mpc = thermalManager.temp_hotend[0].mpc; - - CAN_Send_Gcode_2params('M', 306, 'A', mpc.ambient_xfer_coeff_fan0, 'C', mpc.block_heat_capacity); // M306 R A - CAN_Send_Gcode_2params('M', 306, 'F', mpc.fanCoefficient(), 'H', mpc.filament_heat_capacity_permm); // M306 F H - CAN_Send_Gcode_2params('M', 306, 'P', mpc.heater_power, 'R', mpc.sensor_responsiveness); // M306 P C - #endif - - //CAN_Send_Gcode_2params('M', 150, 0, 0, 0, 0); // M150, SWITCH NEOPIXEL OFF - - /* - extern Planner planner; // M92 Steps per mm - CAN_Send_Gcode_2params('M', 92, 'X', planner.settings.axis_steps_per_mm[X_AXIS], 'Y', planner.settings.axis_steps_per_mm[Y_AXIS]); - CAN_Send_Gcode_2params('M', 92, 'Z', planner.settings.axis_steps_per_mm[Z_AXIS], 'E', planner.settings.axis_steps_per_mm[E_AXIS]); - - // M200 Set filament diameter - CAN_Send_Gcode_2params('M', 200, 'S', parser.volumetric_enabled, 'D', LINEAR_UNIT(planner.filament_size[0])); - #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) - CAN_Send_Gcode_2params('M', 200, 'L', LINEAR_UNIT(planner.volumetric_extruder_limit[0]) - #endif - - // M201 Max acceleration - CAN_Send_Gcode_2params('M', 201, 'X', planner.settings.max_acceleration_mm_per_s2[X_AXIS], 'Y', planner.settings.max_acceleration_mm_per_s2[Y_AXIS]); - CAN_Send_Gcode_2params('M', 201, 'Z', planner.settings.max_acceleration_mm_per_s2[Z_AXIS], 'E', planner.settings.max_acceleration_mm_per_s2[E_AXIS]); - - // M203 Max feedrate - CAN_Send_Gcode_2params('M', 203, 'X', planner.settings.max_feedrate_mm_s[X_AXIS], 'Y', planner.settings.max_feedrate_mm_s[Y_AXIS]); - CAN_Send_Gcode_2params('M', 203, 'Z', planner.settings.max_feedrate_mm_s[Z_AXIS], 'E', planner.settings.max_feedrate_mm_s[E_AXIS]); - - // M204 Accelerations in units/sec^2, ENABLED BECAUSE IT INFORMS THE HEAD THE CONFIGURATION WAS SENT - CAN_Send_Gcode_2params('M', 204, 'P', planner.settings.acceleration, 'R', planner.settings.retract_acceleration); - CAN_Send_Gcode_2params('M', 204, 'T', planner.settings.travel_acceleration, 0, 0); - - // M205 - #if ENABLED(CLASSIC_JERK) - CAN_Send_Gcode_2params('M', 205,'S')) planner.settings.min_feedrate_mm_s, 'T')) planner.settings.min_travel_feedrate_mm_s); - CAN_Send_Gcode_2params('M', 205, M205_MIN_SEG_TIME_PARAM, planner.settings.min_segment_time_us, 'J', planner.junction_deviation_mm); - CAN_Send_Gcode_2params('M', 205, 'X', LINEAR_UNIT(planner.max_jerk.x), 'Y', LINEAR_UNIT(planner.max_jerk.y)); - CAN_Send_Gcode_2params('M', 205, 'Z', LINEAR_UNIT(planner.max_jerk.z), 'E', LINEAR_UNIT(planner.max_jerk.e)); - CAN_Send_Gcode_2params('M', 205, 'J', LINEAR_UNIT(planner.junction_deviation_mm), 0, 0); - #endif - - // M206 Home offset - #if DISABLED(NO_HOME_OFFSETS) - _CAN_Send_Gcode_2params('M', 206, 'X', LINEAR_UNIT(home_offset.x), 'Y', LINEAR_UNIT(home_offset.y)); - CAN_Send_Gcode_2params('M', 206, 'Z', LINEAR_UNIT(home_offset.z), 0, 0); - #endif - - // M207 Set Firmware Retraction - // M208 - Firmware Recover - // M209 - Set Auto Retract - - // M220 Speed/feedrate - CAN_Send_Gcode_2params('M', 220, 'S', feedrate_percentage, 0, 0); - - // M221 Flow percentage - CAN_Send_Gcode_2params('M', 221, 'T', 0, 'S', planner.flow_percentage[0]); - // CAN_Send_Gcode_2params('M', 221, 'T', 1, 'S', planner.flow_percentage[1]); // For 2nd extruder - - // M302 Cold extrude settings - #if ENABLED(PREVENT_COLD_EXTRUSION) - CAN_Send_Gcode_2params('M', 302, 'P', '0' + thermalManager.allow_cold_extrude, 'S', thermalManager.extrude_min_temp); // P0 enable cold extrusion checking, P1 = disabled, S=Minimum temperature - #endif - - // M569 TMC Driver StealthChop/SpreadCycle - CAN_Send_Gcode_2params('M', 569, 'S', stepperE0.get_stored_stealthChop(), 'E', 0); // M569 S[0/1] E - - // M592 Nonlinear Extrusion Control - - // M916 TMC Motor current - CAN_Send_Gcode_2params('M', 906, 'E', stepperE0.getMilliamps(), 0, 0); - - // M919 TMC Chopper timing for E only - CAN_Send_Gcode_2params('M', 919, 'O', off, 'P' , Hysteresis End); - CAN_Send_Gcode_2params('M', 919, 'S', Hysteresis Start, 0, 0); - */ - #if ENABLED(USE_CONTROLLER_FAN) - // M710 SIGNALS TO THE HEAD THAT THE CAN CONFIGURATION IS COMPLETE, USE IT AS THE LAST GCODE TO SEND - return CAN_Send_Gcode_2params('M', 710, 'E', controllerFan.settings.extruder_auto_fan_speed, 0, 0); // M710 E - #endif - return HAL_OK; -} - -void CAN_idle() { // Tasks that cannot be done in the ISR - if (CAN_time_sync_request) { - if (CAN_Send_Timestamp() == HAL_OK) { - CAN_time_sync_request = false; - } - } - - if (CAN_head_setup_request) // The head requested the setup data - CAN_Send_Setup(); - - if (string_message_complete) { // Received string message is complete - BUZZ(1, SOUND_OK); - SERIAL_ECHOPGM(">>> CAN MSG FROM HEAD: "); - for (uint32_t i = 0; i < string_message_index; i++) - SERIAL_CHAR(string_message[i]); // Show received string message, ends on '\n' - - string_message_complete = false; - string_message_index = 0; - } - - if ((hcan1.ErrorCode || CAN_head_error || HAL_CAN_error_code) && (millis() - Last_CAN_Error_Message > 6000)) { - BUZZ(1, SOUND_ERROR); - if (CAN_head_error) SERIAL_ECHOLNPGM(">>> CAN Error reported by head"); - CAN_head_error = false; // Reset, but will be repeated by the head - - if (HAL_CAN_error_code) - SERIAL_ECHOLNPGM(">>> HAL CAN Error reported: ", HAL_CAN_error_code); - - if (hcan1.ErrorCode) - SERIAL_ECHOLNPGM(">>> hcan1.ErrorCode=", hcan1.ErrorCode); - - Last_CAN_Error_Message = millis(); - } - - if (ELAPSED(millis(), Next_CAN_Temp_Report)) { // IRON, ERROR, NO TEMP UPDATE RECEIVED IN 10 SECONDS, KILL PRINTER - Next_CAN_Temp_Report = millis() + 10000; - if (FirstE0Error) { // Send error notification - BUZZ(1, SOUND_ERROR); // Warn with sound - SERIAL_ECHOLNPGM("Error: No CAN E0 temp updates!"); - } - else // Send only error message - SERIAL_ECHOLNPGM(">>> CAN ERROR, No E0 temp updates!"); - - FirstE0Error = false; // Warn only once - - #ifndef CAN_DEBUG // Only kill if not debugging - kill(F("CAN ERROR, NO E0 TEMPERATURE UPDATES")); - #endif - } -} - -HAL_StatusTypeDef CAN_Send_Gcode() { // Forward a Marlin Gcode via CAN (uses parser.command_letter, Gcode_no, parser.value_float()) - // Send a Gcode to the head with parameters and values - // Gcode starts with extended frame which can send the Gcode with max 2 parameters and values. - // Extended frames are send to complete all parameters and values (max 2 per extended message). - // 1. Analyze Gcode command - // 2. Ignore gcodes that do not need to be forwarded - // 3. Send parameters and values - // char s[] = "G0 X123.45678 Y124.45678 Z125.45678 E126.45678 F127.45678\n"; - - HAL_StatusTypeDef status = HAL_OK; - uint32_t TxMailbox; // Stores which Mailbox (0-2) is used to store the Sent TX message - - if (parser.command_letter != 'M') // Only forward Mxxx Gcode to head - return HAL_OK; - - uint32_t Gcode_type = GCODE_TYPE_M; // M-code, fixed for now - uint32_t Gcode_no = parser.codenum; - - if (Gcode_no == 109) // Convert M109(Hotend wait) to M104 (no wait) to keep the head responsive - Gcode_no = 104; - - if ((Gcode_no == 501) || (Gcode_no == 502)) // M501=Restore settings, M502=Factory defaults - CAN_head_setup_request = true; // Also update settings for the head - - if ((Gcode_no != 104) && // Set hotend target temp - (Gcode_no != 106) && // Set cooling fan speed - (Gcode_no != 107) && // Cooling fan off - (Gcode_no != 150) && // Set NeoPixel values - //(Gcode_no != 108) && // Break and Continue - (Gcode_no != 280) && // Servo position - (Gcode_no != 306) && // MPC settings/tuning - (Gcode_no != 710) && // Control fan PWM - (Gcode_no != 997)) // Reboot - return HAL_OK; // Nothing to do - - uint32_t index; - uint32_t parameter_counter = 0; - char letters[] = "XYZEFPSABCHIJKLOQRTUVW"; // All possible parameters (22), defines scan order, no "D G M N", includes 'T' for autotune (M306 T) - static uint32_t parameters[8] = { 0 }; // Store found parameters, send max 7 parameters (send in pairs, so reserve 8), CodeA=1 (ASCII65), CodeE=5, CodeF=6, CodeX=88-64=24, CodeY=89-64=25, CodeZ=90-64=26 - static float values[8] = { 0 }; // Store found values, send max 7 parameters (send in pairs, so reserve 8) - - uint8_t CAN_tx_buffer[8]; // 8 bytes CAN data TX buffer - - /* - switch (parser.command_letter) // Filter/adjust Gcodes - { - case 'G': Gcode_type = GCODE_TYPE_G; - switch (Gcode_no) - { - case 12: break; // No Nozzle cleaning support needed on head - case 29: case 34: return HAL_OK; // No bedleveling/Z-syncing on head - break; - } - break; - - case 'M': Gcode_type = GCODE_TYPE_M; - switch (Gcode_no) - { // Save Prog mem: M112, M48, M85, M105, M114, M155, M500, M501, M502, M503, M226, M422 - case 109: Gcode_no = 104; break; // Replace M109 with M104 - case 112: Gcode_no = 104; break; // Don't shutdown board, should stop heating with "M104" - - case 20: case 21: case 22: case 23: case 24: case 25: case 26: case 524: case 540: case 928: - case 27: case 28: case 29: case 30: case 32: case 33: case 34: // No SD file commands - case 43: // No pin debug - case 48: // No repeatability test - case 85: // No inactivity shutdown - case 100: // No show free memory support - case 108: // Break and Continue - case 105: // No temperature reporting - case 114: // Don't report position - case 117: case 118: case 119: // Don't send strings - case 140: case 190: // Ignore bed temp commands - case 150: // Set NeoPixel values - case 154: // No auto position reporting - case 155: // No tempeature reporting - case 226: // Wait for pin state - case 240: // No camera support - case 250: // No LCD contrast support - case 260: case 261: // No I2C on head - case 280: // Don't send servo angle, done via Servo.cpp already - case 290: // No baby stepping - case 300: // No tones - // case 303: // No PID autotune (done on HEAD) - case 304: // No bed PID settings - // case 306: // MPC autotune (done on HEAD) - case 350: case 351: // No live microstepping adjustment - case 380: case 381: // No solenoid support - case 401: case 402: // No probe deploy/stow, done via M280 servo angles - case 412: // Filament runout sensor done by MASTER - case 420: case 421: // No bed leveling state - case 423: // No X Twist Compensation - case 425: // No backlash compensation - case 500: case 501: case 502: case 503: case 504: case 910: // No EEPROM on head, remove M50x commands to save Prog mem - case 510: case 511: case 512: // No locking of the machine - case 605: // No IDEX commands - case 810: case 811: case 812: case 813: case 814: case 815: case 816: case 817: case 818: case 819: - case 851: // - case 871: // No Probe temp config - case 876: // No handle prompt response - case 913: // No Set Hybrid Threshold Speed - case 914: // No TMC Bump Sensitivity - case 997: // No remote reset - case 998: // No ESP3D reset - return HAL_OK; // NO CAM MESSAGE - } - break; - - case 'T': Gcode_type = GCODE_TYPE_T; - switch (Gcode_no) - { - case 0: case 1: - break; - } - break; - - case 'D': Gcode_type = GCODE_TYPE_D; - switch (Gcode_no) - { - case 0: case 1: - break; - } - break; - default: return HAL_OK; // Invalid command, nothing to do - } - */ - - #ifdef CAN_DEBUG - SERIAL_ECHOPGM(">>> CAN GCODE TO HEAD: "); // IRON, DEBUGGING - SERIAL_CHAR(parser.command_letter); - SERIAL_ECHO(Gcode_no); // IRON, DEBUGGING - #endif - - if (strlen(parser.command_ptr) > 4) // "M107\0", ONLY SCAN FOR PARAMETERS IF STRING IS LONG ENOUGH - for (index = 0; index < sizeof(letters); index++) { // Scan parameters - if (parser.seen(letters[index])) { - parameters[parameter_counter] = letters[index] - 64; // Store parameter letter, A=1, B=2... - - #ifdef CAN_DEBUG - SERIAL_CHAR(' ', letters[index]); // IRON, DEBUGGING - #endif - - if (parser.has_value()) { // Check if there is a value - values[parameter_counter++] = parser.value_float(); - - #ifdef CAN_DEBUG - if (values[parameter_counter - 1] == int(values[parameter_counter - 1])) - SERIAL_ECHO(i16tostr3left(values[parameter_counter - 1])); // Integer value - else - SERIAL_ECHO(p_float_t(values[parameter_counter - 1], 4)); // Float with 4 digits - #endif - - } - else // No value for parameter - values[parameter_counter++] = NAN; // Not A Number, indicates no parameter value is present - } - - if (parameter_counter == 8) { // Max is 7 parameters - parameter_counter--; - SERIAL_ECHOLNPGM("\nError: TOO MANY PARAMETERS (> 7): ", parser.command_ptr); - BUZZ(1, SOUND_ERROR); - break; - } - } - #ifdef CAN_DEBUG - SERIAL_EOL(); - #endif - - parameters[parameter_counter] = 0; // Set next parameter to 0 (0=no parameter), send in pairs - index = 0; - float * fp = (float *)CAN_tx_buffer; // Points to TX buffer - - if ((Gcode_no == 710) && (parameters[0] == 3)) // "M710 C" INDICATES REQUEST FOR GCODE COUNT - SERIAL_ECHOLNPGM(">>> GCODES SENT: ", gcode_counter); - - gcode_counter++; - - TxHeader.IDE = CAN_EXTENDED_ID; // Start with EXTENDED_ID then send STANDARD_ID if needed - //TxHeader.ExtId &= EXTID_FIFO_BIT; // Clear ID, keep FIFO bit - TxHeader.ExtId = (TxHeader.ExtId & EXTID_FIFO_BIT) + // KEEP FIFO BIT - ((parameter_counter & PARAMETER_COUNT_MASK) << PARAMETER_COUNT_OFFSET) + // Parameter count - ((Gcode_type & GCODE_TYPE_MASK) << GCODE_TYPE_OFFSET) + // GCODE TYPE (G/M/T/D) - ((Gcode_no & GCODE_NUMBER_MASK) << GCODE_NUMBER_OFFSET) + // GCODE NUMBER - ((parameters[1] & PARAMETER_MASK) << PARAMETER2_OFFSET) + // PARAMETER2 - ((parameters[0] & PARAMETER_MASK) << PARAMETER1_OFFSET); // PARAMETER1 - uint32_t ms = millis(); // Record message send start time - do { - TxHeader.DLC = MIN(8, (parameter_counter - index) << 2); // Maximum 8 bytes, 4 bytes if there is only 1 parameter, 8 bytes if there are 2 - //TxHeader.StdId = (TxHeader.StdId ^ STDID_FIFO_BIT) & STDID_FIFO_BIT; // Toggle FIFO bit 10, clear other bits - TxHeader.StdId = ((TxHeader.StdId ^ STDID_FIFO_BIT) & STDID_FIFO_BIT) + // Toggle FIFO bit - ((parameters[index + 1] & PARAMETER_MASK) << PARAMETER2_OFFSET) + // Parameter 2 - ((parameters[index ] & PARAMETER_MASK) << PARAMETER1_OFFSET); // Parameter 1 - TxHeader.ExtId ^= EXTID_FIFO_BIT; // Toggle FIFO bit 28 - *fp++ = values[index++]; // Copy first parameter value to data, move pointer to next 4 bytes - *fp-- = values[index++]; // Copy 2nd parameter value to data, move pointer to beginning of data array for next round - - while ((HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) == 0) && (millis() - ms < 50)) { } // BLOCKING! Wait max 50ms - status = HAL_CAN_AddTxMessage(&hcan1, &TxHeader, CAN_tx_buffer, &TxMailbox); // Send message - - if (status != HAL_OK) - return status; - - TxHeader.IDE = CAN_STANDARD_ID; // All following messages have standard ID for parameter values, 11 bits identifier - } while (index < parameter_counter); - - return HAL_OK; -} - -void CAN_Send_Position() { // Send the X, Y, Z and E position to the HEAD - CAN_Send_Gcode_2params('G', 92, 'X', current_position.x, 'Y', current_position.y); // M92 X Y - CAN_Send_Gcode_2params('G', 92, 'Z', current_position.z, 'E', current_position.e); // M92 E Z -} - -void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle) { // Called by HAL_CAN_Init - GPIO_InitTypeDef GPIO_InitStruct = {0}; - RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; - - if (canHandle->Instance == CAN1) { - RCC_PeriphCLKInitTypeDef periphClkInit = { }; - HAL_RCCEx_GetPeriphCLKConfig(&periphClkInit); - periphClkInit.PeriphClockSelection |= RCC_APB1ENR_CAN1EN; // DONE IN __HAL_RCC_CAN1_CLK_ENABLE? - - if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) - Error_Handler(); - - // CAN1 clock enable - if (__HAL_RCC_CAN1_IS_CLK_DISABLED()) - __HAL_RCC_CAN1_CLK_ENABLE(); // Enable CAN1 clock - - if (__HAL_RCC_GPIOB_IS_CLK_DISABLED()) // Should be enabled by Marlin already - __HAL_RCC_GPIOB_CLK_ENABLE(); // Enable GPIO B clock - // CAN1 GPIO Configuration - // PB8 ------> CAN1_RX - // PB9 ------> CAN1_TX - - GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9; // Pin PB8 and Pin PB9 - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; - GPIO_InitStruct.Alternate = GPIO_AF9_CAN1; // Alternate function 9 - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // Port B - - // CAN1 interrupt Init - HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 1); - HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn); // Enable CAN FIFO1 interrupt handler - - HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 1, 1); - HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn); // Enable CAN FIFO1 interrupt handler - } -} - -HAL_StatusTypeDef CAN1_Stop() { - return HAL_CAN_Stop(&hcan1); -} - -HAL_StatusTypeDef CAN1_Start() { - HAL_StatusTypeDef status = HAL_OK; - - // Init TxHeader with constant values - TxHeader.ExtId = 0; - TxHeader.StdId = 0; - TxHeader.RTR = CAN_RTR_DATA; // Data transmission type: CAN_RTR_DATA / CAN_RTR_REMOTE - TxHeader.TransmitGlobalTime = DISABLE; // Put timestamp in Data[6-7], requires Time Triggered Communication Mode - - // CAN baud rate = clock frequency / clock divider / prescaler / (1 + TSG1 + TSG2) - // Baud rate = 42M / 3 / 1 / (1 + 11 + 2) = 1M baud - // Baud rate = 42M / 3 / 2 / (1 + 11 + 2) = 500k baud - // Baud rate = 42M / 3 / 4 / (1 + 11 + 2) = 250k baud - hcan1.Instance = CAN1; - hcan1.Init.Prescaler = 3; // 1-1024, 42MHz peripheral clock / 3 --> 14MHz -> 1M baud. 6 --> 500K baud. 12 --> 250K baud. - hcan1.Init.AutoBusOff = DISABLE; // DISABLE: Software controlled Bus-off. ENABLE: Automatic hardware controlled (no send/receive) - hcan1.Init.AutoWakeUp = ENABLE; // ENABLE: Automatic hardware controlled bus wakeup. DISABLE: Software controlled bus wakeup. - hcan1.Init.AutoRetransmission = ENABLE; // DISABLE / ENABLE, resend if transmission failed, but locks up if communication fails/cable not connected!!!!!!!!!!!!!!!!! - hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ; // CAN_SJW_1TQ - hcan1.Init.TimeSeg1 = CAN_BS1_11TQ; // CAN_BS1_11TQ - hcan1.Init.TimeSeg2 = CAN_BS2_2TQ; // CAN_BS2_2TQ - hcan1.Init.Mode = CAN_MODE_NORMAL; // CAN_MODE_NORMAL / CAN_MODE_SILENT / CAN_MODE_LOOPBACK / CAN_MODE_SILENT_LOOPBACK - hcan1.Init.TimeTriggeredMode = DISABLE; // TTCAN is used to assign timeslot to the devices for real time applications - hcan1.Init.ReceiveFifoLocked = DISABLE; // Handle RX FIFO overruns. DISABLE: Overwrite previous message with new one. ENABLE: Discard the new message. - hcan1.Init.TransmitFifoPriority = ENABLE; // Handle TX FIFO send order. ENABLE: Chronologically. DISABLE: Transmit lower ID number first. - - status = HAL_CAN_Init(&hcan1); // Calls HAL_CAN_MspInit - if (status != HAL_OK) - return status; - - CAN_FilterTypeDef sFilterConfig; - - // Catch CAN messags with highest bit of StdId set in FIFO0 - sFilterConfig.FilterBank = 0; // This filter bank ID number (0-13 for single CAN instances) - sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // Accept if "Received ID" & Mask = ID, CAN_FILTERMODE_IDMASK / CAN_FILTERMODE_IDLIST (See Figure 342 in RM0090) - sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // CAN_FILTERSCALE_16BIT / CAN_FILTERSCALE_32BIT (See Figure 342 in RM0090) - sFilterConfig.FilterIdHigh = 0b1000000000000000; // ID MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) - sFilterConfig.FilterIdLow = 0; // ID LSB: (0-0xFFFF) ([ExtId12-0][IDE][RTR] 0) (0="don't care") - sFilterConfig.FilterMaskIdHigh = 0b1000000000000000; // Mask MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) - sFilterConfig.FilterMaskIdLow = 0; // Mask LSB: (0-0xFFFF) ([ExtId12-0][IDE][RTR] 0) (0="don't care") - sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; // Store message in FIFO1 (CAN_FILTER_FIFO0 / CAN_FILTER_FIFO1) - sFilterConfig.FilterActivation = CAN_FILTER_ENABLE; // CAN_FILTER_ENABLE / CAN_FILTER_DISABLE - sFilterConfig.SlaveStartFilterBank = 14; // Start bank number for CAN slave instance (not used in single CAN setups) - HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig); - - // Catch all remaining CAN messages in FIFO1 - sFilterConfig.FilterBank = 1; // This filter bank ID number (0-13 for single CAN instances) - sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // CAN_FILTERMODE_IDMASK / CAN_FILTERMODE_IDLIST (See Figure 342 in RM0090) - sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // CAN_FILTERSCALE_16BIT / CAN_FILTERSCALE_32BIT (See Figure 342 in RM0090) - sFilterConfig.FilterIdHigh = 0b0000; // ID MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) - sFilterConfig.FilterIdLow = 0x0000; // ID LSB: (0-0xFFFF) ([ExtId12-0][IDE][RTR] 0) (0="don't care") - sFilterConfig.FilterMaskIdHigh = 0b0000; // Mask MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) - sFilterConfig.FilterMaskIdLow = 0x0000; // Mask LSB: (0-0xFFFF) ([ExtId12-0][IDE][RTR] 0) (0="don't care") - sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO1; // Store message in FIFO0 (CAN_FILTER_FIFO0 / CAN_FILTER_FIFO1) - sFilterConfig.FilterActivation = CAN_FILTER_ENABLE; // CAN_FILTER_ENABLE / CAN_FILTER_DISABLE - sFilterConfig.SlaveStartFilterBank = 14; // Start bank number for CAN slave instance (not used in single CAN setups) - HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig); - - // Activate RX FIFO0/FIFO1 new message interrupt - HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING); // Calls CAN1_RX0_IRQHandler -> HAL_CAN_IRQHandler -> HAL_CAN_RxFifo0MsgPendingCallback - HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO1_MSG_PENDING); // Calls CAN1_RX1_IRQHandler -> HAL_CAN_IRQHandler -> HAL_CAN_RxFifo1MsgPendingCallback - -// Activate RX FIFO0/FIFO1 overrun interrupt - HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_OVERRUN); // Calls CAN1_RX0_IRQHandler -> HAL_CAN_ErrorCallback - HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO1_OVERRUN); // Calls CAN1_RX1_IRQHandler -> HAL_CAN_ErrorCallback - - HAL_CAN_ActivateNotification(&hcan1, CAN_IT_ERROR); // Calls CAN1_RX0_IRQHandler -> HAL_CAN_ErrorCallback - - status = HAL_CAN_Start(&hcan1); // Start the CAN1 module - if (status != HAL_OK) - return status; - - return CAN_Send_Gcode_2params('M', 997, 0, 0, 0, 0); // M997, reset head at host startup -} - -void HAL_CAN_RxFifoMsgPending(CAN_HandleTypeDef *hcan, uint32_t RxFifo) { // ISR! New FIFO 0/1 message interrupt handler - CAN_RxHeaderTypeDef RxHeader; // RX Header buffer for FIFO0 - uint8_t CAN_RX_buffer_Fifo[8]; // CAN MESSAGE DATA BUFFER - - if (HAL_CAN_GetRxMessage(&hcan1, RxFifo, &RxHeader, CAN_RX_buffer_Fifo) == HAL_OK) { // Get message from CAN_RX_FIFO0 - if ((RxHeader.StdId & CAN_IO_MASK) != CAN_io_state) { // First handle time critical IO update - CAN_io_state = (RxHeader.StdId & CAN_IO_MASK); - endstops.update(); - } - - if (RxHeader.StdId & CAN_STRING_MESSAGE_MASK) { // Head sends a string message - char * CAN_RX_p = (char *)CAN_RX_buffer_Fifo; - for (uint32_t i = 0; i < RxHeader.DLC; i++) { - string_message[string_message_index++ % 128] = CAN_RX_p[i]; // Copy message to global buffer - - if (CAN_RX_p[i] == '\n') { - string_message_complete = true; // Print buffer - string_message[string_message_index % 128] = 0; // Close string with \0 - } - } - } - else if (RxHeader.DLC == 4) { // FIFO0, head sent a temperature update (DLC = Data Length Code == 4 bytes) - float * fp = (float *)CAN_RX_buffer_Fifo; // FIFO0 - thermalManager.temp_hotend[0].celsius = *fp; // Set E0 hotend temperature - Next_CAN_Temp_Report = millis() + 10000; - FirstE0Error = true; // Reset error status - } - - if (RxHeader.StdId & CAN_REQUEST_TIME_SYNC_MASK) {; // FIFO0, head signals request for time stamp - time_sync_request_time = micros(); // Record the time sync request receive time - CAN_time_sync_request = true; - } - - CAN_head_setup_request = (RxHeader.StdId & CAN_REQUEST_SETUP_MASK) > 0; // FIFO0, head signals request for setup data - CAN_head_error = (RxHeader.StdId & CAN_ERROR_MASK) > 0; // FIFO0, head signals an error - } -} - -void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { // ISR! New FIFO0 message interrupt handler - HAL_CAN_RxFifoMsgPending(hcan, CAN_RX_FIFO0); -} - -void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan) { // ISR! New FIFO1 message interrupt handler - HAL_CAN_RxFifoMsgPending(hcan, CAN_RX_FIFO1); -} - -void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) { // Interrupt handler for any CAN error - HAL_CAN_error_code = hcan->ErrorCode; // Store the received error code -} - -/* -CAN Bus Control functions - HAL_StatusTypeDef HAL_CAN_Start(CAN_HandleTypeDef *hcan) Start the CAN module - HAL_StatusTypeDef HAL_CAN_Stop(CAN_HandleTypeDef *hcan) Stop the CAN module - HAL_StatusTypeDef HAL_CAN_RequestSleep(CAN_HandleTypeDef *hcan) Request sleep mode entry. - HAL_StatusTypeDef HAL_CAN_WakeUp(CAN_HandleTypeDef *hcan) Wake up from sleep mode. - uint32_t HAL_CAN_IsSleepActive(CAN_HandleTypeDef *hcan) Check is sleep mode is active. - HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox) Add a message to the Tx mailboxes and activate the corresponding transmission request - HAL_StatusTypeDef HAL_CAN_AbortTxRequest(CAN_HandleTypeDef *hcan, uint32_t TxMailboxes) Abort transmission request - uint32_t HAL_CAN_GetTxMailboxesFreeLevel(CAN_HandleTypeDef *hcan) Return Tx mailboxes free level - uint32_t HAL_CAN_IsTxMessagePending(CAN_HandleTypeDef *hcan, uint32_t TxMailboxes);Check if a transmission request is pending on the selected Tx mailbox - uint32_t HAL_CAN_GetTxTimestamp(CAN_HandleTypeDef *hcan, uint32_t TxMailbox) Return Tx Timestamp - HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[]) Get a CAN frame from the Rx FIFO - uint32_t HAL_CAN_GetRxFifoFillLevel(CAN_HandleTypeDef *hcan, uint32_t RxFifo) Return Rx FIFO fill level - -CAN INTERRUPT FUNCTIONS (See STM32F4xx_hal_can.c) - HAL_StatusTypeDef HAL_CAN_ActivateNotification(CAN_HandleTypeDef *hcan, uint32_t ActiveITs) // Enable interrupts - HAL_StatusTypeDef HAL_CAN_DeactivateNotification(CAN_HandleTypeDef *hcan, uint32_t InactiveITs) // Disable interrupts - -CAN WEAK CALLBACKS WHEN USING STANDARD WEAK CAN1_RX0_IRQHandler------------> INTERRUPTS - __weak void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan) // CAN_IT_TX_MAILBOX_EMPTY - __weak void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef *hcan) // CAN_IT_TX_MAILBOX_EMPTY - __weak void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef *hcan) // CAN_IT_TX_MAILBOX_EMPTY - __weak void HAL_CAN_TxMailbox0AbortCallback(CAN_HandleTypeDef *hcan) // CAN_IT_TX_MAILBOX_EMPTY - __weak void HAL_CAN_TxMailbox1AbortCallback(CAN_HandleTypeDef *hcan) // CAN_IT_TX_MAILBOX_EMPTY - __weak void HAL_CAN_TxMailbox2AbortCallback(CAN_HandleTypeDef *hcan) // CAN_IT_TX_MAILBOX_EMPTY - __weak void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) // CAN_IT_RX_FIFO0_MSG_PENDING - __weak void HAL_CAN_RxFifo0FullCallback(CAN_HandleTypeDef *hcan) // CAN_IT_RX_FIFO0_FULL - __weak void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan) // CAN_IT_RX_FIFO1_MSG_PENDING - __weak void HAL_CAN_RxFifo1FullCallback(CAN_HandleTypeDef *hcan) // CAN_IT_RX_FIFO1_FULL - __weak void HAL_CAN_SleepCallback(CAN_HandleTypeDef *hcan) // CAN_IT_SLEEP_ACK - __weak void HAL_CAN_WakeUpFromRxMsgCallback(CAN_HandleTypeDef *hcan) // CAN_IT_WAKEUP - __weak void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) // CAN_IT_ERROR -*/ - -#endif // CAN_MASTER diff --git a/Marlin/src/HAL/STM32/CAN_host.cpp b/Marlin/src/HAL/STM32/CAN_host.cpp new file mode 100644 index 000000000000..abef545d721e --- /dev/null +++ b/Marlin/src/HAL/STM32/CAN_host.cpp @@ -0,0 +1,780 @@ +/** + * 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 . + * + */ + +/** + * Contributor Notes: + * NOTE 1: For MKS Monster 8 V1/V2 on Arduino use: Board "Generic STM32F4 series", Board part number "Generic F407VETx" + * NOTE 2: Requires `HAL_CAN_MODULE_ENABLED`, e.g., with `-DHAL_CAN_MODULE_ENABLED` + * For Arduino IDE use "hal_conf_extra.h" with `#define HAL_CAN_MODULE_ENABLED` + * NOTE 3: To accept all CAN messages, enable 1 filter (FilterBank = 0) in "FilterMode = CAN_FILTERMODE_IDMASK", mask and ID = 0 (0=don't care) + * NOTE 4: Serial communication in ISR causes issues! Hangs etc. so avoid! + * NOTE 5: A FIFO storage cell is called a "Mailbox" in STM32F4xx, FIFO0 and FIFO1 can (onlyd)hold 3 CAN messages each. + * NOTE 6: The filter ID/mask numbers (LOW/HIGH) do not directly relate to the message ID numbers (See Figure 342 in RM0090) + */ + +#include "../../inc/MarlinConfigPre.h" + +#if ALL(CAN_HOST, STM32F4xx) + +#include "../platforms.h" +#include "../../gcode/parser.h" +#include "../../module/temperature.h" +#include "../../module/motion.h" // For current_position variable +#include "../../module/planner.h" // For steps/mm parameters variables +#include "../../feature/tmc_util.h" +#include "../../module/endstops.h" +#include "../../feature/controllerfan.h" // For controllerFan settings +#include "../../libs/numtostr.h" // For float to string conversion + +#include "../shared/CAN_host.h" + +// Interrupt handlers controlled by the CAN_IER register +extern "C" void CAN1_RX0_IRQHandler(void); // CAN1 FIFO0 interrupt handler (new message, full, overrun) +extern "C" void CAN1_RX1_IRQHandler(void); // CAN1 FIFO1 interrupt handler (new message, full, overrun) +extern "C" void CAN1_SCE_IRQHandler(void); // CAN1 status change interrupt handler + +extern "C" void CAN2_RX0_IRQHandler(void); // CAN2 FIFO0 interrupt handler (new message, full, overrun) +extern "C" void CAN2_RX1_IRQHandler(void); // CAN2 FIFO1 interrupt handler (new message, full, overrun) +extern "C" void CAN2_SCE_IRQHandler(void); // CAN2 status change error interrupt handler (See CAN_ESR/CAN_MSR registers) + +extern "C" void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan); // CAN FIFO0 new message callback +extern "C" void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan); // CAN FIFO1 new message callback +extern "C" void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan); // CAN error interrupt callback + +//#define CAN_LED_PIN PC8 + +#ifndef CAN_BAUDRATE + #define CAN_BAUDRATE 1000000 +#endif + +#if (CAN_BAUDRATE != 1000000) && (CAN_BAUDRATE != 500000) && (CAN_BAUDRATE != 250000) && (CAN_BAUDRATE != 125000) + #error ERROR: Select a valid CAN_BAUDRATE: 1000000, 500000, 250000 or 125000 baud +#endif + +#define STDID_FIFO_TOGGLE_BIT 0b10000000000 +#define EXTID_FIFO_TOGGLE_BIT 0x10000000 + +#define CAN_HOST_FIFO_DEPTH 3 // RX and TX FIFO0 and FIFO1 depth + +CAN_HandleTypeDef hCAN1 = { 0 }; // The global CAN handle +CAN_TxHeaderTypeDef TxHeader = { 0 }; // Header to send a CAN message +volatile uint32_t CAN_io_state = 0; // Virtual IO state variable +volatile bool CAN_toolhead_error = 0; // Register if an error was reported by the toolhead +volatile bool CAN_toolhead_setup_request = false; // Signals the toolhead requested setup information +volatile bool CAN_time_sync_request = false; // Signals the toolhead requested a time sync +volatile uint32_t time_sync_request_time = 0; // Record the time the time sync request was received + +volatile uint32_t HAL_CAN_error_code = 0; // Store the HAL CAN error code +volatile uint32_t CAN_host_error_code = 0; // Store the CAN host error code + +volatile bool first_E0_error = true; // First CAN bus error, show warning only once +volatile bool string_message_complete = false; // Signals a complete string message was received +volatile uint32_t string_message_index = 0; // Index into the CAN string that is being received +uint32_t CAN_next_temp_report_time = 12000; // Track when the next toolhead temperature report should have arrived +uint32_t CAN_next_error_message_time = 0; // Track when to display the next repeat of an error message +volatile bool CAN_host_FIFO_toggle_bit = false; // FIFO toggle flag for receiver FIFO filtering +char string_message[CAN_HOST_MAX_STRING_MSG_LENGTH] = "\0"; // CAN string message buffer for incoming messages + +uint32_t CAN_host_get_iostate() { + return CAN_io_state; +} + +uint32_t CAN_set_extended_id(int gcode_type, int gcode_no, int parameter1, int parameter2, int count) { + + CAN_host_FIFO_toggle_bit = !CAN_host_FIFO_toggle_bit; // FIFO toggle bit + + return (CAN_host_FIFO_toggle_bit ? EXTID_FIFO_TOGGLE_BIT : 0) | // FIFO toggle bit + (count << CAN_ID_PARAMETER_COUNT_BIT_POS) | // Parameter count + (gcode_type << CAN_ID_GCODE_TYPE_BIT_POS) | // G/M/T/D-code + ((gcode_no & CAN_ID_GCODE_NUMBER_MASK) << CAN_ID_GCODE_NUMBER_BIT_POS) | // Gcode number + ((parameter1 & CAN_ID_PARAMETER_LETTER_MASK) << CAN_ID_PARAMETER1_BIT_POS) | // First parameter + ((parameter2 & CAN_ID_PARAMETER_LETTER_MASK) << CAN_ID_PARAMETER2_BIT_POS); // Second parameter + +} + +// Send time sync timestamp of arrival and response time +void CAN_host_send_timestamp() { // Request receive timestamp + request response timestamp + + uint32_t TxMailbox; // Stores which Mailbox (0-2) was used to store the sent message + + TxHeader.IDE = CAN_ID_EXT; + TxHeader.DLC = 8; // Send sync time t1(receive time, uint32_t) and t2(response time, uint32_t) + TxHeader.ExtId = CAN_set_extended_id(CAN_ID_GCODE_TYPE_M, CAN_HOST_GCODE_TIME_SYNC_NO, 1, 2, 2); + + uint8_t CAN_tx_buffer[8]; // 8 bytes CAN data TX buffer + uint32_t * uint32p = (uint32_t *)CAN_tx_buffer; // Point to TX buffer + *uint32p++ = time_sync_request_time; + + uint32_t deadline = millis() + CAN_HOST_MAX_WAIT_TIME; + while ((HAL_CAN_GetTxMailboxesFreeLevel(&hCAN1) < CAN_HOST_FIFO_DEPTH) && PENDING(millis(), deadline)) { /* BLOCKING! Wait for empty TX buffer */ } + + if (HAL_CAN_GetTxMailboxesFreeLevel(&hCAN1)) { + *uint32p = micros(); // Only record the response time at the last possible moment + HAL_CAN_AddTxMessage(&hCAN1, &TxHeader, CAN_tx_buffer, &TxMailbox); // Queue CAN message + } + else + CAN_host_error_code |= CAN_ERROR_TX_MSG_DROPPED; + +} + +// Send specified Gcode with max 2 parameters and 2 values via CAN bus +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 status = HAL_OK; + + switch (Gcode_type) { + + case 'D': + Gcode_type = CAN_ID_GCODE_TYPE_D; + return HAL_ERROR; + break; + + case 'G': + Gcode_type = CAN_ID_GCODE_TYPE_G; + return HAL_ERROR; + break; + + case 'M': + Gcode_type = CAN_ID_GCODE_TYPE_M; + + #if ENABLED(CAN_DEBUG) + SERIAL_ECHOPGM("; CAN TO TOOLHEAD: \"M", Gcode_no); + if (parameter1) { + SERIAL_CHAR(' ', parameter1); + if (value1 == int(value1)) + SERIAL_ECHO(int(value1)); // Integer value + else + SERIAL_ECHO(p_float_t(value1, 4)); // Float with 4 digits + } + + if (parameter2) { + SERIAL_CHAR(' ', parameter2); + if (value2 == int(value2)) + SERIAL_ECHO(int(value2)); // Integer value + else + SERIAL_ECHO(p_float_t(value2, 4)); // Float with 4 digits + } + SERIAL_ECHOLN("\""); + #endif + + break; + + case 'T': Gcode_type = CAN_ID_GCODE_TYPE_T; + return HAL_ERROR; + break; + + default: + return HAL_ERROR; // Unknown Gcode type + } + + if (parameter1 > 31) + parameter1 -= 64; // Format 'A' = 1, 'B' = 2, etc. + + if (parameter2 > 31) + parameter2 -= 64; // Format 'A' = 1, 'B' = 2, etc. + + TxHeader.IDE = CAN_ID_EXT; + TxHeader.DLC = 4 * (!!parameter1 + !!parameter2); // Amount of bytes to send (4 or 8) + + TxHeader.ExtId = CAN_set_extended_id(Gcode_type, Gcode_no, parameter1, parameter2, TxHeader.DLC >> 2); + + uint8_t CAN_tx_buffer[8]; // 8 bytes CAN data TX buffer + float * fp = (float *)CAN_tx_buffer; // Point to TX buffer + *fp++ = value1; + *fp = value2; + + uint32_t TxMailbox; // Stores which Mailbox (0-2) was used to store the sent message + const uint32_t deadline = millis() + CAN_HOST_MAX_WAIT_TIME; + while ((HAL_CAN_GetTxMailboxesFreeLevel(&hCAN1) == 0) && PENDING(millis(), deadline)) { /* BLOCKING! Wait for empty TX buffer */ } + + if (HAL_CAN_GetTxMailboxesFreeLevel(&hCAN1)) + status = HAL_CAN_AddTxMessage(&hCAN1, &TxHeader, CAN_tx_buffer, &TxMailbox); // Queue CAN message + else + CAN_host_error_code |= CAN_ERROR_TX_MSG_DROPPED; + + return status; +} + +void CAN_host_send_setup() { // Send setup to toolhead + + // NOTE: Sending many command too fast will cause a Marlin command buffer overrun at the toolhead, add delays if needed + CAN_toolhead_setup_request = false; + + SERIAL_ECHOLNPGM(">>> CAN: Sending configuration to toolhead..."); + + #if ENABLED(MPCTEMP) + // M306 MPC settings (managed by host) + MPC_t &mpc = thermalManager.temp_hotend[0].mpc; + + CAN_host_send_gcode_2params('M', 306, 'A', mpc.ambient_xfer_coeff_fan0, 'C', mpc.block_heat_capacity); // M306 R A + CAN_host_send_gcode_2params('M', 306, 'F', mpc.fanCoefficient(), 'H', mpc.filament_heat_capacity_permm); // M306 F H + CAN_host_send_gcode_2params('M', 306, 'P', mpc.heater_power, 'R', mpc.sensor_responsiveness); // M306 P C + + #endif + + //CAN_host_send_gcode_2params('M', 150, 0, 0, 0, 0); // M150, SWITCH NEOPIXEL OFF + + /* + extern Planner planner; // M92 Steps per mm + CAN_host_send_gcode_2params('M', 92, 'X', planner.settings.axis_steps_per_mm[X_AXIS], 'Y', planner.settings.axis_steps_per_mm[Y_AXIS]); + CAN_host_send_gcode_2params('M', 92, 'Z', planner.settings.axis_steps_per_mm[Z_AXIS], 'E', planner.settings.axis_steps_per_mm[E_AXIS]); + + // M200 Set filament diameter + CAN_host_send_gcode_2params('M', 200, 'S', parser.volumetric_enabled, 'D', LINEAR_UNIT(planner.filament_size[0])); + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + CAN_host_send_gcode_2params('M', 200, 'L', LINEAR_UNIT(planner.volumetric_extruder_limit[0]) + #endif + + // M201 Max acceleration + CAN_host_send_gcode_2params('M', 201, 'X', planner.settings.max_acceleration_mm_per_s2[X_AXIS], 'Y', planner.settings.max_acceleration_mm_per_s2[Y_AXIS]); + CAN_host_send_gcode_2params('M', 201, 'Z', planner.settings.max_acceleration_mm_per_s2[Z_AXIS], 'E', planner.settings.max_acceleration_mm_per_s2[E_AXIS]); + + // M203 Max feedrate + CAN_host_send_gcode_2params('M', 203, 'X', planner.settings.max_feedrate_mm_s[X_AXIS], 'Y', planner.settings.max_feedrate_mm_s[Y_AXIS]); + CAN_host_send_gcode_2params('M', 203, 'Z', planner.settings.max_feedrate_mm_s[Z_AXIS], 'E', planner.settings.max_feedrate_mm_s[E_AXIS]); + + // M204 Accelerations in units/sec^2, ENABLED BECAUSE IT INFORMS THE TOOLHEAD THE CONFIGURATION WAS SENT + CAN_host_send_gcode_2params('M', 204, 'P', planner.settings.acceleration, 'R', planner.settings.retract_acceleration); + CAN_host_send_gcode_2params('M', 204, 'T', planner.settings.travel_acceleration, 0, 0); + + // M205 + #if ENABLED(CLASSIC_JERK) + CAN_host_send_gcode_2params('M', 205,'S')) planner.settings.min_feedrate_mm_s, 'T')) planner.settings.min_travel_feedrate_mm_s); + CAN_host_send_gcode_2params('M', 205, M205_MIN_SEG_TIME_PARAM, planner.settings.min_segment_time_us, 'J', planner.junction_deviation_mm); + CAN_host_send_gcode_2params('M', 205, 'X', LINEAR_UNIT(planner.max_jerk.x), 'Y', LINEAR_UNIT(planner.max_jerk.y)); + CAN_host_send_gcode_2params('M', 205, 'Z', LINEAR_UNIT(planner.max_jerk.z), 'E', LINEAR_UNIT(planner.max_jerk.e)); + CAN_host_send_gcode_2params('M', 205, 'J', LINEAR_UNIT(planner.junction_deviation_mm), 0, 0); + #endif + + // M206 Home offset + #if DISABLED(NO_HOME_OFFSETS) + CAN_host_send_gcode_2params('M', 206, 'X', LINEAR_UNIT(home_offset.x), 'Y', LINEAR_UNIT(home_offset.y)); + CAN_host_send_gcode_2params('M', 206, 'Z', LINEAR_UNIT(home_offset.z), 0, 0); + #endif + + // M207 Set Firmware Retraction + // M208 - Firmware Recover + // M209 - Set Auto Retract + + // M220 Speed/feedrate + CAN_host_send_gcode_2params('M', 220, 'S', feedrate_percentage, 0, 0); + + // M221 Flow percentage + CAN_host_send_gcode_2params('M', 221, 'T', 0, 'S', planner.flow_percentage[0]); + // CAN_host_send_gcode_2params('M', 221, 'T', 1, 'S', planner.flow_percentage[1]); // For 2nd extruder + + // M302 Cold extrude settings + #if ENABLED(PREVENT_COLD_EXTRUSION) + CAN_host_send_gcode_2params('M', 302, 'P', '0' + thermalManager.allow_cold_extrude, 'S', thermalManager.extrude_min_temp); // P0 enable cold extrusion checking, P1 = disabled, S=Minimum temperature + #endif + + // M569 TMC Driver StealthChop/SpreadCycle + CAN_host_send_gcode_2params('M', 569, 'S', stepperE0.get_stored_stealthChop(), 'E', 0); // M569 S[0/1] E + + // M592 Nonlinear Extrusion Control + + // M916 TMC Motor current + CAN_host_send_gcode_2params('M', 906, 'E', stepperE0.getMilliamps(), 0, 0); + + // M919 TMC Chopper timing for E only + CAN_host_send_gcode_2params('M', 919, 'O', off, 'P' , Hysteresis End); + CAN_host_send_gcode_2params('M', 919, 'S', Hysteresis Start, 0, 0); + */ + + #if ALL(USE_CONTROLLER_FAN, CONTROLLER_FAN_EDITABLE) + CAN_host_send_gcode_2params('M', 710, 'E', controllerFan.settings.extruder_auto_fan_speed, 'P', controllerFan.settings.probing_auto_fan_speed); + #endif + + // Signals to the toolhead that the configuration is complete, use it as the last Gcode to send + CAN_host_send_gcode_2params('M', CAN_HOST_CONFIGURATION_COMPLETE, 0, 0, 0, 0); +} + +void CAN_host_idle() { // Tasks that can/should not be done in the ISR + + if (CAN_time_sync_request) { // Send time sync timestamps + CAN_time_sync_request = false; + CAN_host_send_timestamp(); + } + + if (string_message_complete) { // Received string message is complete, show the string + BUZZ(1, SOUND_OK); + SERIAL_ECHOPGM(">>> CAN toolhead MSG: "); + + for (uint32_t i = 0; i < string_message_index; i++) + SERIAL_CHAR(string_message[i]); // Show received string message, ends on '\n' + + string_message_complete = false; // Get ready for the next string + string_message_index = 0; + } + + // Report time sync results + if ((hCAN1.ErrorCode || CAN_toolhead_error || HAL_CAN_error_code) && ELAPSED(millis(), CAN_next_error_message_time)) { + + BUZZ(1, SOUND_ERROR); + + if (CAN_toolhead_error) { + SERIAL_ECHOLNPGM(">>> CAN Error reported by toolhead"); + CAN_toolhead_error = false; // Reset, but will be repeated by the toolhead + } + + if (HAL_CAN_error_code) + SERIAL_ECHOLNPGM(">>> HAL CAN Error reported: ", HAL_CAN_error_code); + + if (CAN_host_error_code) + SERIAL_ECHOLNPGM(">>> HOST CAN Error Code: ", CAN_host_error_code); + + if (hCAN1.ErrorCode) + SERIAL_ECHOLNPGM(">>> CAN Error Code = ", hCAN1.ErrorCode); + + CAN_next_error_message_time = millis() + CAN_HOST_ERROR_REPEAT_TIME; + } + + if (ELAPSED(millis(), CAN_next_temp_report_time)) { + + CAN_next_temp_report_time = millis() + CAN_HOST_ERROR_REPEAT_TIME; + if (first_E0_error) { // Send error notification + BUZZ(1, SOUND_ERROR); // Warn with sound + SERIAL_ECHOLNPGM("Error: No CAN E0 temp updates"); + } + else // Send only error message + SERIAL_ECHOLNPGM(">>> CAN error: No E0 temp updates"); + + first_E0_error = false; // Warn only once + + #if DISABLED(CAN_DEBUG) // Only kill if not debugging + kill(F("CAN error: No E0 tempeature updates")); + #endif + + if (CAN_toolhead_setup_request) // The toolhead requested the setup configuration + CAN_host_send_setup(); + } +} + +HAL_StatusTypeDef CAN_host_send_gcode() { // Forward a Marlin Gcode via CAN (uses parser.command_letter, Gcode_no, parser.value_float()) + // Send a Gcode to the toolhead with parameters and values + // Gcode starts with extended frame which can send the Gcode with max 2 parameters and values. + // Extended frames are send to complete all parameters and values (max 2 per extended message). + // 1. Analyze Gcode command + // 2. Ignore gcodes that do not need to be forwarded + // 3. Send parameters and values + // char s[] = "G0 X123.45678 Y124.45678 Z125.45678 E126.45678 F127.45678\n"; + + HAL_StatusTypeDef status = HAL_OK; + uint32_t TxMailbox; // Stores which Mailbox (0-2) was used to store the sent message + + if (parser.command_letter != 'M') // Only forward Mxxx Gcode to toolhead + return HAL_OK; + + uint32_t Gcode_type = CAN_ID_GCODE_TYPE_M; // M-code, fixed for now + uint32_t Gcode_no = parser.codenum & CAN_ID_GCODE_NUMBER_MASK; + + if (Gcode_no == 109) // Convert M109(Hotend wait) to M104 (no wait) to keep the toolhead responsive + Gcode_no = 104; + + if ((Gcode_no == 501) || (Gcode_no == 502)) // M501=Restore settings, M502=Factory defaults + CAN_toolhead_setup_request = true; // Also update settings for the toolhead + + if ((Gcode_no != 104) && // Set hotend target temp + (Gcode_no != 106) && // Set cooling fan speed + (Gcode_no != 107) && // Cooling fan off + (Gcode_no != 115) && // Firmware info (testing) + (Gcode_no != 119) && // Endstop status (testing) + (Gcode_no != 150) && // Set NeoPixel values + //(Gcode_no != 108) && // Break and Continue + (Gcode_no != 280) && // Servo position + (Gcode_no != 306) && // MPC settings/tuning + (Gcode_no != 710) && // Control fan PWM + (Gcode_no != 997)) // Reboot + return HAL_OK; // Nothing to do + + uint32_t index; + uint32_t parameter_counter = 0; + char letters[] = "XYZEFPSABCHIJKLOQRTUVW"; // All possible parameters (22), defines scan order, no "D G M N", includes 'T' for autotune (M306 T) + static uint32_t parameters[8] = { 0 }; // Store found parameters, send max 7 parameters (send in pairs, so reserve 8), CodeA=1 (ASCII65), CodeE=5, CodeF=6, CodeX=88-64=24, CodeY=89-64=25, CodeZ=90-64=26 + static float values[8] = { 0 }; // Store found values, send max 7 parameters (send in pairs, so reserve 8) + + uint8_t CAN_tx_buffer[8]; // 8 bytes CAN data TX buffer + + /* + switch (parser.command_letter) // Filter/adjust Gcodes + { + case 'G': Gcode_type = CAN_ID_GCODE_TYPE_G; + switch (Gcode_no) + { + case 12: break; // No Nozzle cleaning support needed on toolhead + case 29: case 34: return HAL_OK; // No bedleveling/Z-syncing on toolhead + break; + } + break; + + case 'M': Gcode_type = CAN_ID_GCODE_TYPE_M; + switch (Gcode_no) + { // Save Prog mem: M112, M48, M85, M105, M114, M155, M500, M501, M502, M503, M226, M422 + case 109: Gcode_no = 104; break; // Replace M109 with M104 + case 112: Gcode_no = 104; break; // Don't shutdown board, should stop heating with "M104" + + case 20: case 21: case 22: case 23: case 24: case 25: case 26: case 524: case 540: case 928: + case 27: case 28: case 29: case 30: case 32: case 33: case 34: // No SD file commands + case 43: // No pin debug + case 48: // No repeatability test + case 85: // No inactivity shutdown + case 100: // No show free memory support + case 108: // Break and Continue + case 105: // No temperature reporting + case 114: // Don't report position + case 117: case 118: case 119: // Don't send strings + case 140: case 190: // Ignore bed temp commands + case 150: // Set NeoPixel values + case 154: // No auto position reporting + case 155: // No tempeature reporting + case 226: // Wait for pin state + case 240: // No camera support + case 250: // No LCD contrast support + case 260: case 261: // No I2C on toolhead + case 280: // Don't send servo angle, done via Servo.cpp already + case 290: // No baby stepping + case 300: // No tones + // case 303: // No PID autotune (done on TOOLHEAD) + case 304: // No bed PID settings + // case 306: // MPC autotune (done on TOOLHEAD) + case 350: case 351: // No live microstepping adjustment + case 380: case 381: // No solenoid support + case 401: case 402: // No probe deploy/stow, done via M280 servo angles + case 412: // Filament runout sensor done by MASTER + case 420: case 421: // No bed leveling state + case 423: // No X Twist Compensation + case 425: // No backlash compensation + case 500: case 501: case 502: case 503: case 504: case 910: // No EEPROM on toolhead, remove M50x commands to save Prog mem + case 510: case 511: case 512: // No locking of the machine + case 605: // No IDEX commands + case 810: case 811: case 812: case 813: case 814: case 815: case 816: case 817: case 818: case 819: + case 851: // + case 871: // No Probe temp config + case 876: // No handle prompt response + case 913: // No Set Hybrid Threshold Speed + case 914: // No TMC Bump Sensitivity + case 997: // No remote reset + case 998: // No ESP3D reset + return HAL_OK; // NO CAN MESSAGE + } + break; + + case 'T': Gcode_type = CAN_ID_GCODE_TYPE_T; + switch (Gcode_no) + { + case 0: case 1: + break; + } + break; + + case 'D': Gcode_type = CAN_ID_GCODE_TYPE_D; + switch (Gcode_no) + { + case 0: case 1: + break; + } + break; + default: return HAL_OK; // Invalid command, nothing to do + } + */ + + #if ENABLED(CAN_DEBUG) + SERIAL_ECHOPGM(">>> CAN Gcode to toolhead: "); + SERIAL_CHAR(parser.command_letter); + SERIAL_ECHO(Gcode_no); + #endif + + if (strlen(parser.command_ptr) > 4) // "M107\0", Only scan for parameters if the string is long enough + for (index = 0; index < sizeof(letters); index++) { // Scan parameters + if (parser.seen(letters[index])) { + parameters[parameter_counter] = (letters[index] - 64) & CAN_ID_PARAMETER_LETTER_MASK; // Store parameter letter, A=1, B=2... + + #if ENABLED(CAN_DEBUG) + SERIAL_CHAR(' ', letters[index]); + #endif + + if (parser.has_value()) { // Check if there is a value + values[parameter_counter++] = parser.value_float(); + + #if ENABLED(CAN_DEBUG) + if (values[parameter_counter - 1] == int(values[parameter_counter - 1])) + SERIAL_ECHO(i16tostr3left(values[parameter_counter - 1])); // Integer value + else + SERIAL_ECHO(p_float_t(values[parameter_counter - 1], 4)); // Float with 4 digits + #endif + } + else // No value for parameter + values[parameter_counter++] = NAN; // Not A Number, indicates no parameter value is present + } + + if (parameter_counter == 8) { // Max is 7 parameters + CAN_host_error_code |= CAN_ERROR_INVALID_GCODE; + parameter_counter--; + SERIAL_ECHOLNPGM("\nError: TOO MANY PARAMETERS (> 7): ", parser.command_ptr); + BUZZ(1, SOUND_ERROR); + break; + } + } + + #if ENABLED(CAN_DEBUG) + SERIAL_EOL(); + #endif + + parameters[parameter_counter] = 0; // Set next parameter to 0 (0=no parameter), send in pairs + index = 0; + float * fp = (float *)CAN_tx_buffer; // Points to TX buffer + + TxHeader.IDE = CAN_ID_EXT; // Start Gcode with Extended ID, then send Standard ID messages if there are more than 2 parameters + + uint32_t deadline = millis() + CAN_HOST_MAX_WAIT_TIME; // Record message send start time + do { + TxHeader.DLC = MIN(8, (parameter_counter - index) << 2); // Maximum 8 bytes, 4 bytes --> only 1 parameter, 8 bytes --> 2 parameters + + if (TxHeader.IDE == CAN_ID_EXT) + TxHeader.ExtId = CAN_set_extended_id(Gcode_type, Gcode_no, parameters[index], parameters[index + 1], parameter_counter); + else { + CAN_host_FIFO_toggle_bit = !CAN_host_FIFO_toggle_bit; + TxHeader.StdId = (CAN_host_FIFO_toggle_bit ? STDID_FIFO_TOGGLE_BIT : 0) | // Toggle bit + (parameters[index ] << CAN_ID_PARAMETER1_BIT_POS) | // Parameter 1 + (parameters[index + 1] << CAN_ID_PARAMETER2_BIT_POS); // Parameter 2 + } + + *fp++ = values[index++]; // Copy first parameter value to data, move pointer to next 4 bytes + *fp-- = values[index++]; // Copy 2nd parameter value to data, move pointer to beginning of data array for next round + + while ((HAL_CAN_GetTxMailboxesFreeLevel(&hCAN1) == 0) && PENDING(millis(), deadline)) { /* BLOCKING! Wait for emtpy TX buffer */ } + + if (HAL_CAN_GetTxMailboxesFreeLevel(&hCAN1)) + status = HAL_CAN_AddTxMessage(&hCAN1, &TxHeader, CAN_tx_buffer, &TxMailbox); // Queue CAN message + else + CAN_host_error_code |= CAN_ERROR_TX_MSG_DROPPED; + + if (status != HAL_OK) return status; + + TxHeader.IDE = CAN_ID_STD; // All following messages have standard ID for parameter values, 11 bits identifier + } while (index < parameter_counter); + + return status; + +} // CAN_host_send_gcode + +void CAN_host_send_position() { // Send the X, Y, Z and E position to the TOOLHEAD + CAN_host_send_gcode_2params('G', 92, 'X', current_position.x, 'Y', current_position.y); // M92 X Y + CAN_host_send_gcode_2params('G', 92, 'Z', current_position.z, 'E', current_position.e); // M92 E Z +} + +// TODO: SETUP HARDWARE BASED ON CAN_RX, CAN_TX PINS +void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle) { // Called by HAL_CAN_Init + + if (canHandle->Instance == CAN1) { + if (__HAL_RCC_CAN1_IS_CLK_DISABLED()) + __HAL_RCC_CAN1_CLK_ENABLE(); // Enable CAN1 clock + + if (__HAL_RCC_GPIOB_IS_CLK_DISABLED()) + __HAL_RCC_GPIOB_CLK_ENABLE(); // Enable GPIO B clock + // CAN1 GPIO Configuration + // PB8 ------> CAN1_RX + // PB9 ------> CAN1_TX + + GPIO_InitTypeDef GPIO_InitStruct = { 0 }; + GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9; // Pin PB8 and Pin PB9 + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF9_CAN1; // Alternate function 9 + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // Port B + + // Enable the CAN interrupt handlers + HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 1); + HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn); // Enable CAN1 FIFO0 interrupt handler + + HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 1, 1); // Set CAN interrupt priority + HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn); // Enable CAN1 FIFO1 interrupt handler + } +} + +HAL_StatusTypeDef CAN_host_stop() { + return HAL_CAN_Stop(&hCAN1); +} + +HAL_StatusTypeDef CAN_host_start() { + + HAL_StatusTypeDef status = HAL_OK; + + // Initialize TxHeader with constant values + TxHeader.ExtId = 0; + TxHeader.StdId = 0; + TxHeader.RTR = CAN_RTR_DATA; // Data transmission type: CAN_RTR_DATA / CAN_RTR_REMOTE + TxHeader.TransmitGlobalTime = DISABLE; // Put timestamp in Data[6-7], requires Time Triggered Communication Mode + + // CAN peripheral clock is 42MHz (168Mhz / 4) + // CAN baud rate = clock frequency / clock divider / prescaler / (1 + TSG1 + TSG2) + // Baud rate = 42M / 3 / 1 / (1 + 11 + 2) = 1M baud (Sample point = 12/14=86%) + // Baud rate = 42M / 3 / 2 / (1 + 11 + 2) = 500k baud + // Baud rate = 42M / 3 / 4 / (1 + 11 + 2) = 250k baud + hCAN1.Instance = CAN1; + hCAN1.Init.Prescaler = 3; // 1-1024, 42MHz peripheral clock / 3 --> 14MHz -> 1M baud. 6 --> 500K baud. 12 --> 250K baud. + hCAN1.Init.AutoBusOff = DISABLE; // DISABLE: Software controlled Bus-off. ENABLE: Automatic hardware controlled (no send/receive) + hCAN1.Init.AutoWakeUp = ENABLE; // ENABLE: Automatic hardware controlled bus wakeup. DISABLE: Software controlled bus wakeup. + hCAN1.Init.AutoRetransmission = ENABLE; // DISABLE / ENABLE, resend if transmission failed, but locks up if communication fails/cable not connected!!!!!!!!!!!!!!!!! + hCAN1.Init.SyncJumpWidth = CAN_SJW_1TQ; // CAN_SJW_1TQ (1-4) Should be 1 + hCAN1.Init.TimeSeg1 = CAN_BS1_11TQ; // CAN_BS1_11TQ (1-16) + hCAN1.Init.TimeSeg2 = CAN_BS2_2TQ; // CAN_BS2_2TQ (1-8) + hCAN1.Init.Mode = CAN_MODE_NORMAL; // CAN_MODE_NORMAL / CAN_MODE_SILENT / CAN_MODE_LOOPBACK / CAN_MODE_SILENT_LOOPBACK + hCAN1.Init.TimeTriggeredMode = DISABLE; // TTCAN is used to assign timeslot to the devices for real time applications + hCAN1.Init.ReceiveFifoLocked = DISABLE; // Handle RX FIFO overruns. DISABLE: Overwrite previous message with new one. ENABLE: Discard the new message. + hCAN1.Init.TransmitFifoPriority = ENABLE; // Handle TX FIFO send order. ENABLE: Chronologically. DISABLE: Transmit lower ID number first. + + status = HAL_CAN_Init(&hCAN1); // Calls HAL_CAN_MspInit + if (status != HAL_OK) return status; + + CAN_FilterTypeDef sFilterConfig; + + // Store CAN messags with highest bit of StdId set in FIFO0 + sFilterConfig.FilterBank = 0; // This filter bank ID number (0-13 for single CAN instances) + sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // Accept if "Received ID" & Mask = ID, CAN_FILTERMODE_IDMASK / CAN_FILTERMODE_IDLIST (See Figure 342 in RM0090) + sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // CAN_FILTERSCALE_16BIT / CAN_FILTERSCALE_32BIT (See Figure 342 in RM0090) + sFilterConfig.FilterIdHigh = 0b1000000000000000; // ID MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) + sFilterConfig.FilterIdLow = 0; // ID LSB: (0-0xFFFF) ([ExtId12-0][IDE][RTR] 0) (0="don't care") + sFilterConfig.FilterMaskIdHigh = 0b1000000000000000; // Mask MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) + sFilterConfig.FilterMaskIdLow = 0; // Mask LSB: (0-0xFFFF) ([ExtId12-0][IDE][RTR] 0) (0="don't care") + sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; // Store message in FIFO1 (CAN_FILTER_FIFO0 / CAN_FILTER_FIFO1) + sFilterConfig.FilterActivation = CAN_FILTER_ENABLE; // CAN_FILTER_ENABLE / CAN_FILTER_DISABLE + sFilterConfig.SlaveStartFilterBank = 0; // Start bank number for CAN slave instance (not used in single CAN setups) + status = HAL_CAN_ConfigFilter(&hCAN1, &sFilterConfig); + if (status != HAL_OK) return status; + + // Store all remaining CAN messages in FIFO1 + sFilterConfig.FilterBank = 1; // This filter bank ID number (0-13 for single CAN instances) +//sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // CAN_FILTERMODE_IDMASK / CAN_FILTERMODE_IDLIST (See Figure 342 in RM0090) +//sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // CAN_FILTERSCALE_16BIT / CAN_FILTERSCALE_32BIT (See Figure 342 in RM0090) + sFilterConfig.FilterIdHigh = 0; // ID MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) +//sFilterConfig.FilterIdLow = 0; // ID LSB: (0-0xFFFF) ([ExtId12-0][IDE][RTR] 0) (0="don't care") + sFilterConfig.FilterMaskIdHigh = 0; // Mask MSB: (0-0xFFFF) (StdId[10-0] [ExtId17-13]) (See Figure 342 in RM0090) +//sFilterConfig.FilterMaskIdLow = 0; // Mask LSB: (0-0xFFFF) ([ExtId12-0][IDE][RTR] 0) (0="don't care") + sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO1; // Store message in FIFO0 (CAN_FILTER_FIFO0 / CAN_FILTER_FIFO1) +//sFilterConfig.FilterActivation = CAN_FILTER_ENABLE; // CAN_FILTER_ENABLE / CAN_FILTER_DISABLE +//sFilterConfig.SlaveStartFilterBank = 0; // Start bank number for CAN slave instance (not used in single CAN setups) + status = HAL_CAN_ConfigFilter(&hCAN1, &sFilterConfig); + if (status != HAL_OK) return status; + + // Activate RX FIFO0/FIFO1 new message interrupt + status = HAL_CAN_ActivateNotification(&hCAN1, CAN_IT_RX_FIFO0_MSG_PENDING); // Calls CAN1_RX0_IRQHandler / CAN2_RX0_IRQHandler + if (status != HAL_OK) return status; + + status = HAL_CAN_ActivateNotification(&hCAN1, CAN_IT_RX_FIFO1_MSG_PENDING); // Calls CAN1_RX1_IRQHandler / CAN2_RX0_IRQHandler + if (status != HAL_OK) return status; + + // Activate RX FIFO0/FIFO1 overrun interrupt + status = HAL_CAN_ActivateNotification(&hCAN1, CAN_IT_RX_FIFO0_OVERRUN); // Calls CAN1_RX0_IRQHandler / CAN2_RX0_IRQHandler + if (status != HAL_OK) return status; + + status = HAL_CAN_ActivateNotification(&hCAN1, CAN_IT_RX_FIFO1_OVERRUN); // Calls CAN1_RX1_IRQHandler / CAN2_RX0_IRQHandler + if (status != HAL_OK) return status; + + status = HAL_CAN_ActivateNotification(&hCAN1, CAN_IT_ERROR); // Calls CAN1_RX0_IRQHandler / CAN2_RX0_IRQHandler + if (status != HAL_OK) return status; + + status = HAL_CAN_Start(&hCAN1); // Start the CAN module + if (status != HAL_OK) return status; + + #ifdef CAN_LED_PIN + pinMode(CAN_LED_PIN, OUTPUT); + #endif + + #ifdef FDCAN_LED_PIN + pinMode(FDCAN_LED_PIN, OUTPUT); + #endif + status = CAN_host_send_gcode_2params('M', 997, 0, 0, 0, 0); // M997, reset toolhead at host startup + + return status; +}// CAN_host_start() + +void CAN_host_read_message(CAN_HandleTypeDef *hcan, uint32_t RxFifo) { // ISR! FIFO 0/1 CAN message interrupt handler + + CAN_RxHeaderTypeDef RxHeader; + uint8_t CAN_RX_buffer_FIFO[8]; // CAN message buffer + + if (HAL_CAN_GetRxFifoFillLevel(hcan, RxFifo) && + (HAL_CAN_GetRxMessage(hcan, RxFifo, &RxHeader, CAN_RX_buffer_FIFO) == HAL_OK)) { + + if ((RxHeader.StdId & CAN_ID_IO_MASK) != CAN_io_state) { // First handle time critical virtual IO update + CAN_io_state = (RxHeader.StdId & CAN_ID_IO_MASK); + endstops.update(); + } + + if (RxHeader.StdId & CAN_ID_STRING_MESSAGE_MASK) { // Toolhead sends a string message + char * CAN_RX_p = (char *)CAN_RX_buffer_FIFO; + for (uint32_t i = 0; i < RxHeader.DLC; i++) { + string_message[string_message_index++ % CAN_HOST_MAX_STRING_MSG_LENGTH] = CAN_RX_p[i]; // Copy message to global string buffer + + if (CAN_RX_p[i] == '\n') { + string_message_complete = true; // String is complete, idle task can show the string + string_message[string_message_index % CAN_HOST_MAX_STRING_MSG_LENGTH] = 0; // Close string with \0 + } + } + } + else if (RxHeader.DLC == 4) { // Only 1 record, so it's a temperature update (DLC = Data Length Code is 4 bytes) + float *fp = (float *)CAN_RX_buffer_FIFO; + thermalManager.temp_hotend[0].celsius = *fp; // Set E0 hotend temperature from received message + CAN_next_temp_report_time = millis() + CAN_HOST_E0_TEMP_UPDATE_WATCHDOG_TIME; // A temp update must be received within this window + first_E0_error = true; // Reset error status + } + + if (RxHeader.StdId & CAN_ID_REQUEST_TIME_SYNC_MASK) { // Toolhead signals request for time stamp + time_sync_request_time = micros(); // Record the time sync request receive time + CAN_time_sync_request = true; + } + + CAN_toolhead_setup_request = (RxHeader.StdId & CAN_ID_REQUEST_SETUP_MASK) > 0; // Toolhead requests setup configuration + + CAN_toolhead_error = (RxHeader.StdId & CAN_ID_ERROR_MASK) > 0; // Toolhead signals an error + } +} + +void CAN1_RX0_IRQHandler() { // ISR! CAN FIFO0 interrupt handler (overrides weak function) + + HAL_CAN_IRQHandler(&hCAN1); // Forward call for callbacks --> HAL_CAN_RxFifo0MsgPendingCallback/HAL_CAN_ErrorCallback + // OR + //HAL_CAN_RxFifo0MsgPendingCallback(&hCAN1); // Call the required callback directly, faster but no error reporting +} + +void CAN1_RX1_IRQHandler() { // ISR! CAN FIFO1 Interrupt handler (overrides weak function) + + HAL_CAN_IRQHandler(&hCAN1); // Forward call for callbacks --> HAL_CAN_RxFifo1MsgPendingCallback/HAL_CAN_ErrorCallback + // OR + //HAL_CAN_RxFifo1MsgPendingCallback(&hCAN1); // Call the required callback directly, faster but no error reporting +} + +void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { // ISR! New FIFO0 message interrupt handler + CAN_host_read_message(hcan, CAN_RX_FIFO0); +} + +void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan) { // ISR! New FIFO1 message interrupt handler + CAN_host_read_message(hcan, CAN_RX_FIFO1); +} + +void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) { // ISR! Interrupt handler for any CAN error + HAL_CAN_error_code = hcan->ErrorCode; // Store the received error code +} + +#endif // CAN_HOST && STM32F4xx diff --git a/Marlin/src/HAL/STM32/FDCAN.cpp b/Marlin/src/HAL/STM32/FDCAN.cpp deleted file mode 100644 index 395b1c8694c4..000000000000 --- a/Marlin/src/HAL/STM32/FDCAN.cpp +++ /dev/null @@ -1,594 +0,0 @@ -/** - * 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/MarlinConfigPre.h" - -#if ENABLED(FDCAN_TOOLHEAD) - -#include "../platforms.h" -#include "../../gcode/gcode.h" -#include "../../gcode/parser.h" -#include "../../gcode/queue.h" -#include "../../module/temperature.h" -#include "../../libs/numtostr.h" -#include "../../inc/MarlinConfig.h" -#include "../../feature/controllerfan.h" -#include "../../core/serial.h" - -#include "../shared/FDCAN.h" - -#define FDCAN_RX_FIFO0_MASK (FDCAN_IR_RF0L | FDCAN_IR_RF0F | FDCAN_IR_RF0N) // Fifo 0: Message lost | Fifo full | New message -#define FDCAN_RX_FIFO1_MASK (FDCAN_IR_RF1L | FDCAN_IR_RF1F | FDCAN_IR_RF1N) // Fifo 1: Message lost | Fifo full | New message -#define FDCAN_RX_FIFO_MASK (FDCAN_RX_FIFO0_MASK | FDCAN_RX_FIFO1_MASK) // Fifo : Message lost | Fifo full | New message -#define HAL_TIM_FLAG_ALL (TIM_FLAG_CC1 | TIM_FLAG_CC2 | TIM_FLAG_CC3 | TIM_FLAG_CC4 | TIM_FLAG_UPDATE | TIM_FLAG_BREAK | TIM_FLAG_BREAK2 | TIM_FLAG_TRIGGER | TIM_FLAG_COM) - -#define FDCAN_MAX_WAIT_TIME 25 // Amount of ms to wait for for resources (buffers/command buffers) -#define CAN_TIMESTAMP_NO 7777 // M7777 is used to handle timesync message - -// IMPORTANT NOTE -// =============== -// 1. In \Users\\.platformio\packages\framework-arduinoststm32@4.20600.231001\libraries\SrcWrapper\src\HardwareTimer.cpp -// Add function "__weak" in front of "void TIM16_IRQHandler(void)" - -FDCAN_HandleTypeDef hfdcan; - -bool Head_Not_configured = true; // Check if the configuration was send to the head -char gcode_type[4] = { 'D', 'G', 'M', 'D' }; // This way an extended ID is always > 2048 -//char * codeP; // code can walk along with progress in can_gcode -//char can_gcode[MAX_CMD_SIZE + 20]; -char can_gcode_buffer[MAX_CMD_SIZE + 20]; // Gcode that is being received in several CAN messages -volatile uint32_t CAN_Error_Code = 0; // Signals an CAN error occured, report to host -volatile uint32_t Old_Error_Code = 0; // Remember last error code so messages don't repeat -volatile uint32_t gcode_counter = 0; // Count received gcode lines (debugging) -volatile uint32_t CAN_message_counter = 0; // Count sent CAN message (debugging) -volatile bool send_gcode_count = false; -volatile uint32_t t[5] = { 0, 0, 0, 0, 0 }; // Timestamps for time sync -volatile bool CAN_request_time_sync = false; // Request a timestamp -volatile uint32_t time_offset = 0; // Time offset in micro seconds in relation to host time -float drift = 0; // Time sync calculated drift between host and tool head - -extern "C" void TIM16_IRQHandler(void); -extern "C" void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs); -extern "C" void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs); - -void CAN_Add_Parameters(uint32_t parameter, float value, char *codeP) { // Add received CAN parameters to can_gcode string, used in ISR! - *codeP++ = char(parameter + 64); // Add parameter 'X' - - if (!isnan(value)) { // No value behind parameter if value is "Not A Number" - if (value < 0.0) { - *codeP++ = '-'; // Add minus sign to gcode string - value = -value; // Make value positive - } - - // Convert float to string - uint32_t i1 = int(value); // Get integer part of float - itoa(i1, codeP, 10); // Assume 1-4 digits - codeP += strlen(codeP); // Adjust pointer - value = (value - i1); // Get fractional part - - uint32_t j = 0; - uint32_t flag = 0; - for (int i = 0; i < 5; i++) { // 6 digits, 1 extra for rounding - value *= 10.0; - i1 = int(value); - j = (10 * j) + i1; - value = value - i1; - } - - if (value >= 0.5) - j++; // Round up - - if (j) { - *codeP++ = '.'; - if (j < 10000) { - *codeP++ = '0'; - if (j < 1000) { - *codeP++ = '0'; - if (j < 100) { - *codeP++ = '0'; - if (j < 10) - *codeP++ = '0'; - } - } - } - - while (!(j % 10)) // Remove trailing zeros, 120 --> 12 - j = j / 10; - - itoa(j, codeP, 10); - } - } -} - -HAL_StatusTypeDef CAN_Receive(uint32_t Fifo) { // ISR! Process received CAN message in interrupt handler! - HAL_StatusTypeDef status = HAL_OK; - static volatile uint32_t parameter_counter = 0; - char * codeP = can_gcode_buffer; // Put gcode pointer to start of can_gcode_buffer; - uint8_t can_rxbuf[8]; - - FDCAN_RxHeaderTypeDef CanRxHeader; // Receive CAN message buffer - - status = HAL_FDCAN_GetRxMessage(&hfdcan, Fifo, &CanRxHeader, can_rxbuf); - if (status != HAL_OK) - return status; - - uint32_t identifier = CanRxHeader.Identifier; - if (CanRxHeader.IdType == FDCAN_EXTENDED_ID) { // Receiving new gcode - - if (((identifier >> IDENTIFIER_GCODE_NUMBER_OFFSET) & IDENTIFIER_GCODE_MASK) == ((GCODE_TYPE_M << (IDENTIFIER_GCODE_TYPE_OFFSET - IDENTIFIER_GCODE_NUMBER_OFFSET)) + CAN_TIMESTAMP_NO)) { // Time sync response message - uint32_t *uint32p = (uint32_t *)can_rxbuf; - t[3] = micros(); // Record time sync response message receive time - t[1] = *uint32p++; - t[2] = *uint32p; - return status; - } - - // Check for M997 reset command - if (((identifier >> IDENTIFIER_GCODE_NUMBER_OFFSET) & IDENTIFIER_GCODE_MASK) == ((GCODE_TYPE_M << (IDENTIFIER_GCODE_TYPE_OFFSET - IDENTIFIER_GCODE_NUMBER_OFFSET)) + 997)) { // Gcode is M997, Restart the toolhead unconditionally - flashFirmware(0); - while (1); - } - - if (((identifier >> IDENTIFIER_GCODE_NUMBER_OFFSET) & IDENTIFIER_GCODE_MASK) == ((GCODE_TYPE_M << (IDENTIFIER_GCODE_TYPE_OFFSET - IDENTIFIER_GCODE_NUMBER_OFFSET)) + 115)) - CAN_request_time_sync = true; // M115 --> Request a time sync from the host - - memset(can_gcode_buffer, 0, MAX_CMD_SIZE); // Clear buffer - - if (parameter_counter) // Previous gcode must be complete, no more pending parameters - CAN_Error_Code |= CAN_ERROR_INCOMPLETE_GCODE_RECEIVED; - - parameter_counter = (identifier >> IDENTIFIER_PARAMETER_COUNT_OFFSET) & IDENTIFIER_PARAMETER_COUNT_MASK; // Get parameter_counter, bits 25, 26, 27 of 29 bit extended identifier - - *codeP++ = gcode_type[(identifier >> IDENTIFIER_GCODE_TYPE_OFFSET) & IDENTIFIER_GCODE_TYPE_MASK]; // "G" - itoa((identifier >> IDENTIFIER_GCODE_NUMBER_OFFSET) & IDENTIFIER_GCODE_NUMBER_MASK, codeP, 10); // G"92" - - // Check if head configuration was received, marked by M710 command - if (((identifier >> IDENTIFIER_GCODE_NUMBER_OFFSET) & IDENTIFIER_GCODE_MASK) == ((GCODE_TYPE_M << (IDENTIFIER_GCODE_TYPE_OFFSET - IDENTIFIER_GCODE_NUMBER_OFFSET)) + 710)) { // CODE IS M710, M=2, INDICATES CONFIGURATION WAS RECEIVED - Head_Not_configured = false; // HEAD SETUP COMPLETE - send_gcode_count = true; // Report the current received gcode count to the host (debugging) - } - } - - // If present, add parameters to the received gcode - if (parameter_counter && CanRxHeader.DataLength && ((identifier >> IDENTIFIER_PARAMTER1_OFFSET) & IDENTIFIER_PARAMETER_MASK)) { // Get 1st parameter, make sure it's not empty. - codeP += strlen(codeP); // There is already data in the buffer, adjust the pointer - CAN_Add_Parameters(identifier & IDENTIFIER_PARAMETER_MASK, *(float*)(can_rxbuf), codeP); - parameter_counter--; - - if (parameter_counter && (CanRxHeader.DataLength == FDCAN_DLC_BYTES_8) && ((identifier >> IDENTIFIER_PARAMTER2_OFFSET) & IDENTIFIER_PARAMETER_MASK)) { // Get 2nd parameter, make sure it's not empty. - codeP += strlen(codeP); - CAN_Add_Parameters((identifier >> IDENTIFIER_PARAMTER2_OFFSET) & IDENTIFIER_PARAMETER_MASK, *(float*)(can_rxbuf + 4), codeP); - parameter_counter--; - } - } - - if (parameter_counter == 0) { // Gcode is complete, including all parameters, process the gcode - gcode_counter++; - - uint32_t ms = millis(); - bool queue_status; - do { - queue_status = queue.enqueue_one(can_gcode_buffer); // TRUE if the command was queued, FALSE if buffer is full - } - while (!queue_status && (millis() - ms < FDCAN_MAX_WAIT_TIME)); // BLOCKING! Wait for free Marlin command buffer - - if (!queue_status) // Could not store the received CAN command in Marlin command buffer - CAN_Error_Code |= CAN_ERROR_MARLIN_CMD_BUFFER_OVERFLOW; - } - return HAL_OK; -} - -void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) { // ISR! Override "weak" function - if (RxFifo0ITs & FDCAN_IT_RX_FIFO0_MESSAGE_LOST) { // Check the error status first, it might be cleared by reading a message - CAN_Error_Code |= CAN_ERROR_FIFO_OVERFLOW; - __HAL_FDCAN_CLEAR_FLAG(hfdcan, FDCAN_IT_RX_FIFO0_MESSAGE_LOST); // Clear interrupt flag - } - - if (RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) - CAN_Receive(FDCAN_RX_FIFO0); -} - -void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs) { // ISR! Override "weak" function - if (RxFifo1ITs & FDCAN_IT_RX_FIFO1_MESSAGE_LOST) { // Check the error status first, it might be cleared by reading a message - CAN_Error_Code |= CAN_ERROR_FIFO_OVERFLOW; - __HAL_FDCAN_CLEAR_FLAG(hfdcan, FDCAN_IT_RX_FIFO1_MESSAGE_LOST); // Clear interrupt flag - } - - if (RxFifo1ITs & FDCAN_IT_RX_FIFO1_NEW_MESSAGE) - CAN_Receive(FDCAN_RX_FIFO1); -} - -void TIM16_IRQHandler(void) { // ISR! FDCAN INTERRUPT HANDLER, OVERRIDE WEAK FUNCTION - if ((HardwareTimer_Handle[TIMER16_INDEX]) && (HardwareTimer_Handle[TIMER16_INDEX]->handle.Instance->SR & HAL_TIM_FLAG_ALL)) // Interrupt was caused by timer - HAL_TIM_IRQHandler(&HardwareTimer_Handle[TIMER16_INDEX]->handle); // Forward to timer interrupt handler - - uint32_t RxFifoITs = hfdcan.Instance->IR & FDCAN_RX_FIFO_MASK; - RxFifoITs &= hfdcan.Instance->IE; - - if (RxFifoITs) // Any Fifo read interrupts? - HAL_FDCAN_IRQHandler(&hfdcan); // Forward call to FDCAN interrupt handler -} - -//void HAL_FDCAN_ErrorCallback(FDCAN_HandleTypeDef *hfdcan) { // TODO: Catch errors -//} - -//void HAL_FDCAN_ErrorStatusCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ErrorStatusITs) { // TODO: Catch errors -//} - -void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* fdcanHandle) { // Called automatically, configure GPIO for FDCAN, enable interrupts - GPIO_InitTypeDef GPIO_InitStruct = { 0 }; - RCC_PeriphCLKInitTypeDef PeriphClkInit = { 0 }; - - PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_FDCAN; - PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_PCLK1; - - if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) - Error_Handler(); - - // STM32G0B1 and STM32G0 C1 only - // FDCAN1/2 clock enable (one clock for both CAN devices) - if (__HAL_RCC_FDCAN_IS_CLK_DISABLED()) - __HAL_RCC_FDCAN_CLK_ENABLE(); - - if (__HAL_RCC_GPIOB_IS_CLK_DISABLED()) - __HAL_RCC_GPIOB_CLK_ENABLE(); // ENABLE GPIO B CLOCK - // FDCAN2 GPIO Configuration - // PB0 AF3 ------> FDCAN2_RX - // PB1 AF3 ------> FDCAN2_TX - - GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1; // Pin PB0 and Pin PB1 - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // Alternate function - GPIO_InitStruct.Pull = GPIO_NOPULL; // No pullup or pulldown - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // High frequency device - GPIO_InitStruct.Alternate = GPIO_AF3_FDCAN2; // Alternate function 3 - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // Port B - - // FDCAN2 interrupt Init - HAL_NVIC_SetPriority(TIM16_FDCAN_IT0_IRQn, 1, 1); // Set interrupt priority - HAL_NVIC_EnableIRQ(TIM16_FDCAN_IT0_IRQn); // Enable interrupt handler -} - -/* -uint32_t ClockDivider FDCAN kernel clock divider (common to all FDCAN instances, applied only at initialisation of first FDCAN instance) - 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 -uint32_t FrameFormat FDCAN_FRAME_CLASSIC / FDCAN_FRAME_FD_NO_BRS / FDCAN_FRAME_FD_BRS (BRS=Bit Rate Switch) -uint32_t Mode; FDCAN_MODE_NORMAL / FDCAN_MODE_RESTRICTED_OPERATION / FDCAN_MODE_BUS_MONITORING / FDCAN_MODE_INTERNAL_LOOPBACK / FDCAN_MODE_EXTERNAL_LOOPBACK -FunctionalState AutoRetransmission Retransmit if fault is deteced, values: ENABLE / DISABLE -FunctionalState TransmitPause Waits for 2 bit times before transmitting the next frame to allow other notes to cut in, values: ENABLE / DISABLE -FunctionalState ProtocolException Protocol Exception Handling, values: ENABLE(start bus integration) or DISABLE(send error frame) -uint32_t NominalPrescaler Clock divider for generating the bit time quanta (1-512) -uint32_t NominalSyncJumpWidth Maximum number of time quanta the FDCAN allows to lengthen or shorten a bit to perform resynchronization (1-128) -uint32_t NominalTimeSeg1 Number of time quanta in Bit Segment 1 (2-256) -uint32_t NominalTimeSeg2 Number of time quanta in Bit Segment 2 (2-128) -uint32_t DataPrescaler Clock divider for generating the data bit time quanta (1-32) -uint32_t DataSyncJumpWidth Max of time quanta allowed to lengthen or shorten a data bit to perform resynchronization (1-16) -uint32_t DataTimeSeg1 Number of time quanta in Data Bit Segment 1. (1-32) -uint32_t DataTimeSeg2 Number of time quanta in Data Bit Segment 2. (1-16) -uint32_t StdFiltersNbr Number of standard Message ID filters. (0-28) -uint32_t ExtFiltersNbr Number of extended Message ID filters. (0-8) -uint32_t TxFifoQueueMode Tx FIFO/Queue Mode selection FDCAN_TX_FIFO_OPERATION / FDCAN_TX_QUEUE_OPERATION -*/ - -HAL_StatusTypeDef FDCAN2_Start(void) { // START THE FDCAN BUS - HAL_StatusTypeDef status = HAL_OK; - // FDCAN baud rate = Device Clock Frequency / Clock Divider / Prescaler / SJW + TSG1 + TSG2) (SJW=Sync Jump Width) - // Baud rate = 64M / 1 / 4 / (1 + 13 + 2) = 1M (Sample Point = (1 + 13)/(1 + 13 + 2) = 87.5%) - // Baud rate = 64M / 1 / 8 / (1 + 13 + 2) = 500k (Sample Point = (1 + 13)/(1 + 13 + 2) = 87.5%) - // http://www.bittiming.can-wiki.info - // https://www.kvaser.com/support/calculators/can-fd-bit-timing-calculator - hfdcan.Instance = FDCAN2; // FDCAN2 device - hfdcan.Init.ClockDivider = FDCAN_CLOCK_DIV1; // Clock divider 1 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 - hfdcan.Init.FrameFormat = FDCAN_FRAME_CLASSIC; // FDCAN_FRAME_CLASSIC / FDCAN_FRAME_FD_BRS / FDCAN_FRAME_FD_NO_BRS (Bit Rate Switching) - hfdcan.Init.Mode = FDCAN_MODE_NORMAL; // FDCAN_MODE_NORMAL / FDCAN_MODE_EXTERNAL_LOOPBACK / FDCAN_MODE_INTERNAL_LOOPBACK / FDCAN_MODE_BUS_MONITORING / FDCAN_MODE_RESTRICTED_OPERATION - hfdcan.Init.AutoRetransmission = DISABLE; // Auto Retransmission of message if error occured, can cause lockup - hfdcan.Init.TransmitPause = DISABLE; // Transmit Pause to allow for other device to send message - hfdcan.Init.ProtocolException = DISABLE; // ProtocolException - - hfdcan.Init.NominalPrescaler = hfdcan.Init.DataPrescaler = 4; // Arbitration/data clock prescaler (1-16) - hfdcan.Init.NominalSyncJumpWidth = hfdcan.Init.DataSyncJumpWidth = 1; // Arbitration/data sync jump width (1-16) - hfdcan.Init.NominalTimeSeg1 = hfdcan.Init.DataTimeSeg1 = 13; // Arbitration/data period 1 (1-32) - hfdcan.Init.NominalTimeSeg2 = hfdcan.Init.DataTimeSeg2 = 2; // Arbitration/data period 2 (1-16) - - hfdcan.Init.StdFiltersNbr = 2; // Number of standard frame filters - hfdcan.Init.ExtFiltersNbr = 2; // Number of extended frame filters - hfdcan.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; // Queue mode: FDCAN_TX_FIFO_OPERATION / FDCAN_TX_QUEUE_OPERATION - - status = HAL_FDCAN_Init(&hfdcan); - if (status != HAL_OK) - return status; - - FDCAN_FilterTypeDef sFilterConfig; // Configure RX message filter - sFilterConfig.IdType = FDCAN_EXTENDED_ID; // Filter to FIFO1 if higest ID bit is set - sFilterConfig.FilterIndex = 0; // Exteneded filter ID 0 - sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE - sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1; // Filter to FIFO1 - sFilterConfig.FilterID1 = 0x10000000; // Range: 0 - 0x1FFF FFFF, 0=don'tcare - sFilterConfig.FilterID2 = 0x10000000; // Range: 0 - 0x1FFF FFFF, 0=don'tcare - HAL_FDCAN_ConfigFilter(&hfdcan, &sFilterConfig); - - sFilterConfig.IdType = FDCAN_EXTENDED_ID; // Filter all remaining messages to FIFO0 - sFilterConfig.FilterIndex = 1; // Exteneded filter ID 1 - sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE - sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; // Remaining messages go to FIFO0 - sFilterConfig.FilterID1 = 0; // Range: 0 - 0x1FFF FFFF, 0=don'tcare - sFilterConfig.FilterID2 = 0; // Range: 0 - 0x1FFF FFFF, 0=don'tcare - HAL_FDCAN_ConfigFilter(&hfdcan, &sFilterConfig); - - sFilterConfig.IdType = FDCAN_STANDARD_ID; // Filter to FIFO1 if higest ID bit is set - sFilterConfig.FilterIndex = 0; // Standard filter ID 0 - sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE - sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1; // Filter to FIFO1 - sFilterConfig.FilterID1 = 0b10000000000; // Range: 0 - 0x7FF, 0=don't care - sFilterConfig.FilterID2 = 0b10000000000; // Range: 0 - 0x7FF, 0=don't care - HAL_FDCAN_ConfigFilter(&hfdcan, &sFilterConfig); - - sFilterConfig.IdType = FDCAN_STANDARD_ID; // Filter all remaining messages to FIFO0 - sFilterConfig.FilterIndex = 1; // Standard filter ID 1 - sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE - sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; // Remaining messages go to FIFO0 - sFilterConfig.FilterID1 = 0; // Range: 0 - 0x7FF, 0=don't care - sFilterConfig.FilterID2 = 0; // Range: 0 - 0x7FF, 0=don't care - HAL_FDCAN_ConfigFilter(&hfdcan, &sFilterConfig); - - // Start the FDCAN module - status = HAL_FDCAN_Start(&hfdcan); - - // Activate RX FIFO0 new message interrupt - if (status == HAL_OK) - status = HAL_FDCAN_ActivateNotification(&hfdcan, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0); // Calls TIM16_IRQHandler (STM32G0xx) - - // Activate RX FIFO0 message lost interrupt - if (status == HAL_OK) - status = HAL_FDCAN_ActivateNotification(&hfdcan, FDCAN_IT_RX_FIFO0_MESSAGE_LOST, 0); // Calls TIM16_IRQHandler (STM32G0xx) - - // Activate RX FIFO1 new message interrupt - if (status == HAL_OK) - status = HAL_FDCAN_ActivateNotification(&hfdcan, FDCAN_IT_RX_FIFO1_NEW_MESSAGE, 0); // Calls TIM16_IRQHandler (STM32G0xx) - - // Activate RX FIFO1 message lost interrupt - if (status == HAL_OK) - status = HAL_FDCAN_ActivateNotification(&hfdcan, FDCAN_IT_RX_FIFO1_MESSAGE_LOST, 0); // Calls TIM16_IRQHandler (STM32G0xx) - - // TODO: Handle/Catch errors - // if (status == HAL_OK) - // status = HAL_FDCAN_ActivateNotification(&hfdcan, FDCAN_IT_ERROR_PASSIVE, 0); // Calls TIM16_IRQHandler (STM32G0xx) - // FDCAN_IT_LIST_PROTOCOL_ERROR - - return status; -} - -/* ---INTERRUPT HANDLERS------------------------------------------------------------- -void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs); -void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs); -void HAL_FDCAN_RxBufferNewMessageCallback(FDCAN_HandleTypeDef *hfdcan); -void HAL_FDCAN_HighPriorityMessageCallback(FDCAN_HandleTypeDef *hfdcan); - -void HAL_FDCAN_TxEventFifoCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t TxEventFifoITs); -void HAL_FDCAN_TxFifoEmptyCallback(FDCAN_HandleTypeDef *hfdcan); -void HAL_FDCAN_TxBufferCompleteCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t BufferIndexes); -void HAL_FDCAN_TxBufferAbortCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t BufferIndexes); - -void HAL_FDCAN_ErrorCallback(FDCAN_HandleTypeDef *hfdcan); -void HAL_FDCAN_ErrorStatusCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ErrorStatusITs); - -void HAL_FDCAN_ClockCalibrationCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ClkCalibrationITs); -void HAL_FDCAN_TimestampWraparoundCallback(FDCAN_HandleTypeDef *hfdcan); -void HAL_FDCAN_TimeoutOccurredCallback(FDCAN_HandleTypeDef *hfdcan); -void HAL_FDCAN_TT_ScheduleSyncCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t TTSchedSyncITs); -void HAL_FDCAN_TT_TimeMarkCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t TTTimeMarkITs); -void HAL_FDCAN_TT_StopWatchCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t SWTime, uint32_t SWCycleCount); -void HAL_FDCAN_TT_GlobalTimeCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t TTGlobTimeITs); */ - -HAL_StatusTypeDef CAN_Send_Message(bool TempUpdate) { // Called from temperature ISR! - // Send a IO/temp report from the HEAD to the HOST - HAL_StatusTypeDef status = HAL_OK; - FDCAN_TxHeaderTypeDef CanTxHeader; // Transmit CAN message buffer - - // Initialize standard TxHeader values - CanTxHeader.FDFormat = FDCAN_CLASSIC_CAN; // FDCAN_CLASSIC_CAN / FDCAN_FD_CAN - CanTxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; // FDCAN_NO_TX_EVENTS / FDCAN_STORE_TX_EVENTS - CanTxHeader.MessageMarker = 0; // 0-0xFF for tracking messages in FIFO buffer - CanTxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE; // FDCAN_ESI_PASSIVE / FDCAN_ESI_ACTIVE ??? - CanTxHeader.TxFrameType = FDCAN_DATA_FRAME; // FDCAN_DATA_FRAME / FDCAN_REMOTE_FRAME - CanTxHeader.IdType = FDCAN_STANDARD_ID; // FDCAN_STANDARD_ID / FDCAN_EXTENDED_ID - CanTxHeader.BitRateSwitch = FDCAN_BRS_OFF; // FDCAN_BRS_OFF / FDCAN_BRS_ON - - uint8_t can_tx_buffer[8]; - if (TempUpdate) { // Add temperature to CAN message, 4 bytes - float * fp = (float *)can_tx_buffer; // Point to CAN tx buffer - *fp = thermalManager.degHotend(0); // Copy temp to can_tx_buffer - CanTxHeader.DataLength = FDCAN_DLC_BYTES_4; // Hotend temp in data only, bytes, FDCAN_DLC_BYTES_4 = (4 << 16)! - } - else - CanTxHeader.DataLength = FDCAN_DLC_BYTES_0; // 0 data byte CAN message - bool request_time_sync = CAN_request_time_sync; - uint32_t VirtualIO = (Head_Not_configured << CAN_REQUEST_SETUP_BIT) | // Report Head is not configured - (READ(Z_MIN_PROBE_PIN) << CAN_PROBE_BIT) | // Report probe status - (READ(FILAMENT_RUNOUT_PIN) << CAN_FILAMENT_BIT) | // Report filament detector status - (request_time_sync << CAN_REQUEST_TIME_SYNC_BIT) | // Report timesync request - ((!!CAN_Error_Code) << CAN_ERROR_BIT); // Report error (if any) - // ADD PINS YOU WANT TO MONITOR HERE - - CanTxHeader.Identifier = ((CanTxHeader.Identifier ^ STDID_FIFO_BIT) & STDID_FIFO_BIT) + VirtualIO; // Toggle FIFO bit for receiver filtering - uint32_t ms = millis(); - while ((HAL_FDCAN_GetTxFifoFreeLevel(&hfdcan) == 0) && (millis() - ms < FDCAN_MAX_WAIT_TIME)) { } // BLOCKING! Wait for free TX FIFO slot - - if (request_time_sync) { // For time sync, wait until all message TX slots are empty before sending - CAN_request_time_sync = false; - ms = millis(); - while ((HAL_FDCAN_GetTxFifoFreeLevel(&hfdcan) < 3) && (millis() - ms < FDCAN_MAX_WAIT_TIME)) { } // BLOCKING! Wait for free TX FIFO slot - t[0] = micros(); // Store time sync message send time - } - status = HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan, &CanTxHeader, can_tx_buffer); // Send FDCAN message - CAN_message_counter++; - return status; -} - -HAL_StatusTypeDef CAN_Send_String(const char * message) { // Send string message to host, string should end on '\n' - HAL_StatusTypeDef status = HAL_OK; - FDCAN_TxHeaderTypeDef CanTxHeader; // Transmit CAN message buffer - - // Initialize standard TxHeader values - CanTxHeader.FDFormat = FDCAN_CLASSIC_CAN; // FDCAN_CLASSIC_CAN / FDCAN_FD_CAN - CanTxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; // FDCAN_NO_TX_EVENTS / FDCAN_STORE_TX_EVENTS - CanTxHeader.MessageMarker = 0; // 0-0xFF for tracking messages in FIFO buffer - CanTxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE; // FDCAN_ESI_PASSIVE / FDCAN_ESI_ACTIVE ??? - CanTxHeader.TxFrameType = FDCAN_DATA_FRAME; // FDCAN_DATA_FRAME / FDCAN_REMOTE_FRAME - CanTxHeader.IdType = FDCAN_STANDARD_ID; // FDCAN_STANDARD_ID / FDCAN_EXTENDED_ID - CanTxHeader.BitRateSwitch = FDCAN_BRS_OFF; // FDCAN_BRS_OFF / FDCAN_BRS_ON - - uint8_t can_tx_buffer[8]; - int remaining = strlen(message); // Length of string - int index = 0; - - SERIAL_ECHOPGM(">>> SENDING STRING MESSAGE TO HOST: ", message); // IRON, SHOULD HAVE '\n' AT THE END, DEBUGGING - - while (remaining > 0) { // Keep sending message if there are more characters to send - int c = MIN(8, remaining); // Max 8 character at a time - remaining -= c; // Reduce character counter - CanTxHeader.DataLength = (c << DATALENGTH_OFFSET); // Max message length is 8 bytes, offset is 16 bits into the DataLength variable - - for (int i = 0; i < c; i++) - can_tx_buffer[i] = message[index++]; // Copy string data to CAN buffer - - uint32_t VirtualIO = (READ(Z_MIN_PROBE_PIN) << CAN_PROBE_BIT) | // Report probe status - (READ(FILAMENT_RUNOUT_PIN) << CAN_FILAMENT_BIT) | // Report filament detector status - ((!!CAN_Error_Code) << CAN_ERROR_BIT) | // Report error (if any) - (1 << CAN_STRING_MESSAGE_BIT); // Set string message bit - - CanTxHeader.Identifier = ((CanTxHeader.Identifier ^ 0b10000000000) & 0b10000000000) + VirtualIO; // Toggle FIFO bit for receiver filtering - uint32_t ms = millis(); - while ((HAL_FDCAN_GetTxFifoFreeLevel(&hfdcan) < 2) && (millis() - ms < FDCAN_MAX_WAIT_TIME)) { } // BLOCKING! Wait for free TX FIFO slot, with 1 spare slot for urgent messages - - status = HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan, &CanTxHeader, can_tx_buffer); // Send message - CAN_message_counter++; - } - - return status; -} - -uint32_t last_led_flash = 0; // IRON, for error LED flashing -uint32_t last_error_message = 0; -uint32_t oldExtuder_auto_fan_speed = controllerFan.settings.extruder_auto_fan_speed; - -void FDCAN_idle() { // Called from MarlinCore.cpp - - if (oldExtuder_auto_fan_speed != controllerFan.settings.extruder_auto_fan_speed) { - controllerFan.settings.extruder_auto_fan_speed = MAX(controllerFan.settings.extruder_auto_fan_speed, PROBING_AUTO_FAN_SPEED); // Should not be required, safeguard! - oldExtuder_auto_fan_speed = controllerFan.settings.extruder_auto_fan_speed; - } - - if (send_gcode_count) { - MString<24> buffer(F("GCODES="), gcode_counter, '\n'); - CAN_Send_String(buffer); // Report number of gcodes received from host - send_gcode_count = false; - SERIAL_ECHOLNPGM(">>> CAN messages sent: ", CAN_message_counter); - } - // NTP style time sync - // t[0] = local time sync request time - // t[1] = host time sync receive time - // t[2] = host time sync reply time - // t[3] = local time stamp receive time - if (t[3] != 0) { - t[0] += time_offset; // Adjust local time stamps with time_offset - t[3] += time_offset; // Adjust local time stamps with time_offset - uint32_t local_time_adjustment = (t[1] - t[0] + t[2] - t[3]) >> 1; - time_offset += local_time_adjustment; - uint32_t Round_trip_delay = (t[3] - t[0] - t[2] + t[1]); - SERIAL_ECHOLNPGM("t0: ", t[0], " us"); - SERIAL_ECHOLNPGM("t1: ", t[1], " us"); - SERIAL_ECHOLNPGM("t2: ", t[2], " us"); - SERIAL_ECHOLNPGM("t3: ", t[3], " us"); - if (drift) - SERIAL_ECHOLNPGM("Predicted time adjustment: ", drift * float(t[0] - t[4]) / 1000000.0 , " us"); - - SERIAL_ECHOPGM("Local time adjustment: ", local_time_adjustment, " us"); - if (t[4]) { - SERIAL_ECHOLNPGM(" after ", ftostr42_52(float(t[0] - t[4]) / 1000000.0), " seconds"); - drift = local_time_adjustment / (float(t[0] - t[4]) / 1000000.0); // Calculate drift again - SERIAL_ECHOLNPGM("Drift: ", drift, " us/s"); - } - else - SERIAL_EOL(); - - SERIAL_ECHOLNPGM("Time offset: ", time_offset, " us"); - SERIAL_ECHOLNPGM("Round_trip_delay: ", Round_trip_delay, " us"); - SERIAL_ECHOLNPGM("Host response time: ", (t[2] - t[1]), " us"); - - t[4] = t[0]; // Store previous time sync request time - t[3] = 0; - } - - if (hfdcan.ErrorCode) { - uint32_t ms = millis(); - - if ((ms - last_error_message) > 20000) { // 20 seconds repeat - MString<40> buffer(F("Error: fdcan2.ErrorCode="), hfdcan.ErrorCode, '\n'); - CAN_Send_String(buffer); - last_error_message = ms; - } - } - - if (CAN_Error_Code) { - uint32_t ms = millis(); - - if ((ms - last_led_flash) > 100) { // Flash LED fast on error - digitalToggle(LED_PIN); - last_led_flash = ms; - } - - if (CAN_Error_Code != Old_Error_Code) { // Show message on new error code - switch(CAN_Error_Code) { - case CAN_ERROR_FIFO_OVERFLOW: - CAN_Send_String("Error: TOOLHEAD CAN FIFO OVERFLOW\n"); - SERIAL_ECHOLNPGM(">>> ERROR: TOOLHEAD CAN FIFO OVERFLOW"); - break; - - case CAN_ERROR_INCOMPLETE_GCODE_RECEIVED: - CAN_Send_String("Error: TOOLHEAD INCOMPLETE GCODE MESSAGE\n"); - SERIAL_ECHOLNPGM(">>> ERROR: TOOLHEAD INCOMPLETE GCODE MESSAGE"); - break; - - case CAN_ERROR_MARLIN_CMD_BUFFER_OVERFLOW: - CAN_Send_String("Error: TOOLHEAD MARLIN CMD BUF OVERFLOW\n"); - SERIAL_ECHOLNPGM(">>> ERROR: TOOLHEAD MARLIN CMD BUF OVERFLOW"); - break; - - default: { } - } // Switch - - Old_Error_Code = CAN_Error_Code; - } // New error code - } // CAN_Error_Code -} // CAN_idle() - -/* -HAL_StatusTypeDef HAL_FDCAN_ConfigInterruptLines(FDCAN_HandleTypeDef *hfdcan, uint32_t ITList, uint32_t InterruptLine); -HAL_StatusTypeDef HAL_FDCAN_TT_ConfigInterruptLines(FDCAN_HandleTypeDef *hfdcan, uint32_t TTITList, uint32_t InterruptLine); -HAL_StatusTypeDef HAL_FDCAN_ActivateNotification(FDCAN_HandleTypeDef *hfdcan, uint32_t ActiveITs, uint32_t BufferIndexes); -HAL_StatusTypeDef HAL_FDCAN_DeactivateNotification(FDCAN_HandleTypeDef *hfdcan, uint32_t InactiveITs); -HAL_StatusTypeDef HAL_FDCAN_TT_ActivateNotification(FDCAN_HandleTypeDef *hfdcan, uint32_t ActiveTTITs); -HAL_StatusTypeDef HAL_FDCAN_TT_DeactivateNotification(FDCAN_HandleTypeDef *hfdcan, uint32_t InactiveTTITs); -void HAL_FDCAN_IRQHandler(FDCAN_HandleTypeDef *hfdcan); -*/ - -#endif // FDCAN_TOOLHEAD \ No newline at end of file diff --git a/Marlin/src/HAL/STM32/FDCAN_host.cpp b/Marlin/src/HAL/STM32/FDCAN_host.cpp new file mode 100644 index 000000000000..772b66175862 --- /dev/null +++ b/Marlin/src/HAL/STM32/FDCAN_host.cpp @@ -0,0 +1,867 @@ +/** + * 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 . + * + */ + +/** + * Contributor Notes: + * NOTE 1: Requires `HAL_FDCAN_MODULE_ENABLED`, e.g., with `-DFDHAL_CAN_MODULE_ENABLED` + * For Arduino IDE use "hal_conf_extra.h" with `#define HAL_FDCAN_MODULE_ENABLED` + * NOTE 2: Serial communication in ISR causes issues! Hangs etc. so avoid this! + */ + +#include "../../inc/MarlinConfigPre.h" + +#if ALL(CAN_HOST, STM32H7xx) + +#include "../platforms.h" +#include "../../gcode/parser.h" +#include "../../module/temperature.h" +#include "../../module/motion.h" // For current_position variable +#include "../../module/planner.h" // For steps/mm parameters variables +#include "../../feature/tmc_util.h" +#include "../../module/endstops.h" +#include "../../feature/controllerfan.h" // For controllerFan settings +#include "../../libs/numtostr.h" // For float to string conversion + +#include "../shared/CAN_host.h" + +// Interrupt handlers +extern "C" void FDCAN1_IT0_IRQHandler(void); +extern "C" void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs); +extern "C" void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs); + +#ifndef CAN_BAUDRATE + #define CAN_BAUDRATE 1000000 +#endif + +#if (CAN_BAUDRATE != 1000000) && (CAN_BAUDRATE != 500000) && (CAN_BAUDRATE != 250000) && (CAN_BAUDRATE != 125000) + #error ERROR: Select a valid CAN_BAUDRATE: 1000000, 500000, 250000 or 125000 baud +#endif + +//#define CAN_HOST_IO_MASK 0b11111 // Masks for the 5 virtual IO bits (see below) +//#define CAN_HOST_GCODE_NUMBER_MASK 0b1111111111111 +//#define CAN_HOST_PARAMETER_MASK 0b11111 +//#define CAN_HOST_PARAMETER_COUNT_MASK 0b111 +//#define CAN_HOST_GCODE_TYPE_MASK 0b11 + +//#define CAN_HOST_PARAMETER1_BIT_POS 0 +//#define CAN_HOST_PARAMETER2_BIT_POS 5 +//#define CAN_HOST_GCODE_NUMBER_BIT_POS 10 +//#define CAN_HOST_GCODE_TYPE_BIT_POS 23 +//#define CAN_HOST_PARAMETER_COUNT_BIT_POS 25 + +//#define CAN_HOST_GCODE_TYPE_D 0 +//#define CAN_HOST_GCODE_TYPE_G 1 +//#define CAN_HOST_GCODE_TYPE_M 2 +//#define CAN_HOST_GCODE_TYPE_T 3 + +//#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 FDCAN_HOST_TX_FIFO_DEPTH 8 // TX FIFO0 and FIFO1 depth 0-32 +#define FDCAN_HOST_RX_FIFO_DEPTH 8 // RX FIFO0 and FIFO1 depth 0-64 +//#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 FDCAN_HOST_DATALENGTH_OFFSET 16 // Bit offset of FDCAN_DLC_BYTES_1 in register + +//#define CAN_ERROR_RX_FIFO_OVERFLOW (1 << 0) // Incoming message lost +//#define CAN_ERROR_TX_MSG_DROPPED (1 << 1) // Outgoing message dropped +//#define CAN_ERROR_INVALID_GCODE (1 << 2) // Gcode could not be sent over CANBUS +//#define CAN_ERROR_INVALID_BAUDRATE (1 << 3) // Generated baudrate doesn't match CAN_BAUDRATE + +FDCAN_HandleTypeDef hCAN1 = { 0 }; // The global FDCAN handle +FDCAN_TxHeaderTypeDef TxHeader = { 0 }; // Header to send a FDCAN message +volatile uint32_t CAN_io_state = 0; // Virtual IO state variable +volatile bool CAN_toolhead_error = 0; // Register if an error was reported by the toolhead +volatile bool CAN_toolhead_setup_request = false; // Signals the toolhead requested setup information +volatile bool CAN_time_sync_request = false; // Signals the toolhead requested a time sync +volatile uint32_t time_sync_request_time = 0; // Record the time the time sync request was received + +volatile uint32_t HAL_FDCAN_error_code = 0; // Store the HAL FDCAN error code +volatile uint32_t CAN_host_error_code = 0; // Store the CAN host error code + +volatile bool first_E0_error = true; // First CAN bus error, show warning only once +volatile bool string_message_complete = false; // Signals a complete string message was received +volatile uint32_t string_message_index = 0; // Index into the CAN string that is being received +uint32_t CAN_next_temp_report_time = 12000; // Track when the next toolhead temperature report should have arrived +uint32_t CAN_next_error_message_time = 0; // Track when to display the next repeat of an error message +volatile bool CAN_host_FIFO_toggle_bit = false; // FIFO toggle flag for receiver FIFO filtering +char string_message[CAN_HOST_MAX_STRING_MSG_LENGTH] = "\0"; // CAN string message buffer for incoming messages + +uint32_t CAN_host_get_iostate() { + return CAN_io_state; +} + +uint32_t CAN_set_extended_id(int gcode_type, int gcode_no, int parameter1, int parameter2, int count) { + + CAN_host_FIFO_toggle_bit = !CAN_host_FIFO_toggle_bit; // FIFO toggle bit + + return (CAN_host_FIFO_toggle_bit ? EXTID_FIFO_TOGGLE_BIT : 0) | // FIFO toggle bit + (count << CAN_ID_PARAMETER_COUNT_BIT_POS) | // Parameter count + (gcode_type << CAN_ID_GCODE_TYPE_BIT_POS) | // G/M/T/D-code + ((gcode_no & CAN_ID_GCODE_NUMBER_MASK) << CAN_ID_GCODE_NUMBER_BIT_POS) | // Gcode number + ((parameter1 & CAN_ID_PARAMETER_LETTER_MASK) << CAN_ID_PARAMETER1_BIT_POS) | // First parameter + ((parameter2 & CAN_ID_PARAMETER_LETTER_MASK) << CAN_ID_PARAMETER2_BIT_POS); // Second parameter + +} + +// Send time sync timestamp of arrival and response time +void CAN_host_send_timestamp() { // Request receive timestamp + request response timestamp + + TxHeader.IdType = FDCAN_EXTENDED_ID; + TxHeader.DataLength = FDCAN_DLC_BYTES_8; // Send sync time t1(receive time, uint32_t) and t2(response time, uint32_t) + TxHeader.Identifier = CAN_set_extended_id(CAN_ID_GCODE_TYPE_M, CAN_HOST_GCODE_TIME_SYNC_NO, 1, 2, 2); + + uint8_t CAN_tx_buffer[8]; // 8 bytes CAN data TX buffer + uint32_t * uint32p = (uint32_t *)CAN_tx_buffer; // Point to TX buffer + *uint32p++ = time_sync_request_time; + + uint32_t deadline = millis() + CAN_HOST_MAX_WAIT_TIME; + while ((HAL_FDCAN_GetTxFifoFreeLevel(&hCAN1) < FDCAN_HOST_TX_FIFO_DEPTH) && PENDING(millis(), deadline)) { /* BLOCKING! Wait for empty TX buffer */ } + + if (HAL_FDCAN_GetTxFifoFreeLevel(&hCAN1)) { + *uint32p = micros(); // Only record the response time at the last possible moment + HAL_FDCAN_AddMessageToTxFifoQ(&hCAN1, &TxHeader, CAN_tx_buffer); // Queue CAN message + } + else + CAN_host_error_code |= CAN_ERROR_HOST_TX_MSG_DROPPED; + +} + +// Send specified Gcode with max 2 parameters and 2 values via CAN bus +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 status = HAL_OK; + + switch (Gcode_type) { + + case 'D': + Gcode_type = CAN_ID_GCODE_TYPE_D; + return HAL_ERROR; + break; + + case 'G': + Gcode_type = CAN_ID_GCODE_TYPE_G; + return HAL_ERROR; + break; + + case 'M': + Gcode_type = CAN_ID_GCODE_TYPE_M; + + #if ENABLED(CAN_DEBUG) + SERIAL_ECHOPGM("; MSG to toolhead: \"M", Gcode_no); + if (parameter1) { + SERIAL_CHAR(' ', parameter1); + if (value1 == int(value1)) + SERIAL_ECHO(int(value1)); // Integer value + else + SERIAL_ECHO(p_float_t(value1, 4)); // Float with 4 digits + } + + if (parameter2) { + SERIAL_CHAR(' ', parameter2); + if (value2 == int(value2)) + SERIAL_ECHO(int(value2)); // Integer value + else + SERIAL_ECHO(p_float_t(value2, 4)); // Float with 4 digits + } + SERIAL_ECHOLN("\""); + #endif + + break; + + case 'T': Gcode_type = CAN_ID_GCODE_TYPE_T; + return HAL_ERROR; + break; + + default: + return HAL_ERROR; // Unknown Gcode type + } + + if (parameter1 > 31) + parameter1 -= 64; // Format 'A' = 1, 'B' = 2, etc. + + if (parameter2 > 31) + parameter2 -= 64; // Format 'A' = 1, 'B' = 2, etc. + + TxHeader.IdType = FDCAN_EXTENDED_ID; + + TxHeader.DataLength = 4 * (!!parameter1 + !!parameter2) << FDCAN_HOST_DATALENGTH_OFFSET; // Amount of bytes to send (4 or 8) + TxHeader.Identifier = CAN_set_extended_id(Gcode_type, Gcode_no, parameter1, parameter2, !!parameter1 + !!parameter2); + + uint8_t CAN_tx_buffer[8]; // 8 bytes CAN data TX buffer + float * fp = (float *)CAN_tx_buffer; // Point to TX buffer + *fp++ = value1; + *fp = value2; + + const uint32_t deadline = millis() + CAN_HOST_MAX_WAIT_TIME; + while ((HAL_FDCAN_GetTxFifoFreeLevel(&hCAN1) == 0) && PENDING(millis(), deadline)) { /* BLOCKING! Wait for empty TX buffer */ } + + if (HAL_FDCAN_GetTxFifoFreeLevel(&hCAN1)) + status = HAL_FDCAN_AddMessageToTxFifoQ(&hCAN1, &TxHeader, CAN_tx_buffer); // Queue CAN message + else + CAN_host_error_code |= CAN_ERROR_HOST_TX_MSG_DROPPED; + + return status; +} + +void CAN_host_send_setup() { // Send setup to toolhead + + // NOTE: Sending many command too fast will cause a Marlin command buffer overrun at the toolhead, add delays if needed + CAN_toolhead_setup_request = false; + + SERIAL_ECHOLNPGM(">>> CAN: Sending configuration to toolhead..."); + + #if ENABLED(MPCTEMP) + // M306 MPC settings (managed by host) + MPC_t &mpc = thermalManager.temp_hotend[0].mpc; + + CAN_host_send_gcode_2params('M', 306, 'A', mpc.ambient_xfer_coeff_fan0, 'C', mpc.block_heat_capacity); // M306 R A + CAN_host_send_gcode_2params('M', 306, 'F', mpc.fanCoefficient(), 'H', mpc.filament_heat_capacity_permm); // M306 F H + CAN_host_send_gcode_2params('M', 306, 'P', mpc.heater_power, 'R', mpc.sensor_responsiveness); // M306 P C + + #endif + + //CAN_host_send_gcode_2params('M', 150, 0, 0, 0, 0); // M150, SWITCH NEOPIXEL OFF + + /* + extern Planner planner; // M92 Steps per mm + CAN_host_send_gcode_2params('M', 92, 'X', planner.settings.axis_steps_per_mm[X_AXIS], 'Y', planner.settings.axis_steps_per_mm[Y_AXIS]); + CAN_host_send_gcode_2params('M', 92, 'Z', planner.settings.axis_steps_per_mm[Z_AXIS], 'E', planner.settings.axis_steps_per_mm[E_AXIS]); + + // M200 Set filament diameter + CAN_host_send_gcode_2params('M', 200, 'S', parser.volumetric_enabled, 'D', LINEAR_UNIT(planner.filament_size[0])); + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + CAN_host_send_gcode_2params('M', 200, 'L', LINEAR_UNIT(planner.volumetric_extruder_limit[0]) + #endif + + // M201 Max acceleration + CAN_host_send_gcode_2params('M', 201, 'X', planner.settings.max_acceleration_mm_per_s2[X_AXIS], 'Y', planner.settings.max_acceleration_mm_per_s2[Y_AXIS]); + CAN_host_send_gcode_2params('M', 201, 'Z', planner.settings.max_acceleration_mm_per_s2[Z_AXIS], 'E', planner.settings.max_acceleration_mm_per_s2[E_AXIS]); + + // M203 Max feedrate + CAN_host_send_gcode_2params('M', 203, 'X', planner.settings.max_feedrate_mm_s[X_AXIS], 'Y', planner.settings.max_feedrate_mm_s[Y_AXIS]); + CAN_host_send_gcode_2params('M', 203, 'Z', planner.settings.max_feedrate_mm_s[Z_AXIS], 'E', planner.settings.max_feedrate_mm_s[E_AXIS]); + + // M204 Accelerations in units/sec^2, ENABLED BECAUSE IT INFORMS THE TOOLHEAD THE CONFIGURATION WAS SENT + CAN_host_send_gcode_2params('M', 204, 'P', planner.settings.acceleration, 'R', planner.settings.retract_acceleration); + CAN_host_send_gcode_2params('M', 204, 'T', planner.settings.travel_acceleration, 0, 0); + + // M205 + #if ENABLED(CLASSIC_JERK) + CAN_host_send_gcode_2params('M', 205,'S')) planner.settings.min_feedrate_mm_s, 'T')) planner.settings.min_travel_feedrate_mm_s); + CAN_host_send_gcode_2params('M', 205, M205_MIN_SEG_TIME_PARAM, planner.settings.min_segment_time_us, 'J', planner.junction_deviation_mm); + CAN_host_send_gcode_2params('M', 205, 'X', LINEAR_UNIT(planner.max_jerk.x), 'Y', LINEAR_UNIT(planner.max_jerk.y)); + CAN_host_send_gcode_2params('M', 205, 'Z', LINEAR_UNIT(planner.max_jerk.z), 'E', LINEAR_UNIT(planner.max_jerk.e)); + CAN_host_send_gcode_2params('M', 205, 'J', LINEAR_UNIT(planner.junction_deviation_mm), 0, 0); + #endif + + // M206 Home offset + #if DISABLED(NO_HOME_OFFSETS) + CAN_host_send_gcode_2params('M', 206, 'X', LINEAR_UNIT(home_offset.x), 'Y', LINEAR_UNIT(home_offset.y)); + CAN_host_send_gcode_2params('M', 206, 'Z', LINEAR_UNIT(home_offset.z), 0, 0); + #endif + + // M207 Set Firmware Retraction + // M208 - Firmware Recover + // M209 - Set Auto Retract + + // M220 Speed/feedrate + CAN_host_send_gcode_2params('M', 220, 'S', feedrate_percentage, 0, 0); + + // M221 Flow percentage + CAN_host_send_gcode_2params('M', 221, 'T', 0, 'S', planner.flow_percentage[0]); + // CAN_host_send_gcode_2params('M', 221, 'T', 1, 'S', planner.flow_percentage[1]); // For 2nd extruder + + // M302 Cold extrude settings + #if ENABLED(PREVENT_COLD_EXTRUSION) + CAN_host_send_gcode_2params('M', 302, 'P', '0' + thermalManager.allow_cold_extrude, 'S', thermalManager.extrude_min_temp); // P0 enable cold extrusion checking, P1 = disabled, S=Minimum temperature + #endif + + // M569 TMC Driver StealthChop/SpreadCycle + CAN_host_send_gcode_2params('M', 569, 'S', stepperE0.get_stored_stealthChop(), 'E', 0); // M569 S[0/1] E + + // M592 Nonlinear Extrusion Control + + // M916 TMC Motor current + CAN_host_send_gcode_2params('M', 906, 'E', stepperE0.getMilliamps(), 0, 0); + + // M919 TMC Chopper timing for E only + CAN_host_send_gcode_2params('M', 919, 'O', off, 'P' , Hysteresis End); + CAN_host_send_gcode_2params('M', 919, 'S', Hysteresis Start, 0, 0); + */ + + #if ALL(USE_CONTROLLER_FAN, CONTROLLER_FAN_EDITABLE) + CAN_host_send_gcode_2params('M', 710, 'E', controllerFan.settings.extruder_auto_fan_speed, 'P', controllerFan.settings.probing_auto_fan_speed); + #endif + + // Signal to the toolhead that the configuration is complete, use it as the last Gcode to send + CAN_host_send_gcode_2params('M', CAN_HOST_CONFIGURATION_COMPLETE, 0, 0, 0, 0); +} + +void CAN_host_idle() { // Tasks that can/should not be done in the ISR + + if (CAN_time_sync_request) { // Send time sync timestamps + CAN_time_sync_request = false; + CAN_host_send_timestamp(); + } + + if (CAN_toolhead_setup_request) // The toolhead requested the setup configuration + CAN_host_send_setup(); + + if (string_message_complete) { // Received string message is complete, display the string + BUZZ(1, SOUND_OK); + SERIAL_ECHOPGM(">>> CAN toolhead MSG: "); + + for (uint32_t i = 0; i < string_message_index; i++) + SERIAL_CHAR(string_message[i]); // Show received string message, ends on '\n' + + string_message_complete = false; // Get ready for the next string + string_message_index = 0; + } + + // Report time sync results + if ((hCAN1.ErrorCode || CAN_toolhead_error || HAL_FDCAN_error_code) && ELAPSED(millis(), CAN_next_error_message_time)) { + + BUZZ(1, SOUND_ERROR); + + if (CAN_toolhead_error) { + SERIAL_ECHOLNPGM(">>> CAN Error reported by toolhead"); + CAN_toolhead_error = false; // Reset, but will be repeated by the toolhead + } + + if (HAL_FDCAN_error_code) + SERIAL_ECHOLNPGM(">>> HAL CAN Error reported: ", HAL_FDCAN_error_code); + + if (CAN_host_error_code) + SERIAL_ECHOLNPGM(">>> HOST CAN Error Code: ", CAN_host_error_code); + + if (hCAN1.ErrorCode) + SERIAL_ECHOLNPGM(">>> CAN Error Code = ", hCAN1.ErrorCode); + + CAN_next_error_message_time = millis() + CAN_HOST_ERROR_REPEAT_TIME; + } + + if (ELAPSED(millis(), CAN_next_temp_report_time)) { + + CAN_next_temp_report_time = millis() + CAN_HOST_ERROR_REPEAT_TIME; + if (first_E0_error) { // Send error notification + BUZZ(1, SOUND_ERROR); // Warn with sound + SERIAL_ECHOLNPGM("Error: No CAN E0 temp updates"); + } + else // Send only error message + SERIAL_ECHOLNPGM(">>> CAN error: No E0 temp updates"); + + first_E0_error = false; // Warn only once + + #if DISABLED(CAN_DEBUG) // Only kill if not debugging + kill(F("CAN error: No E0 tempeature updates")); + #endif + + if (CAN_toolhead_setup_request) // The toolhead requested the setup configuration + CAN_host_send_setup(); + } +} + +HAL_StatusTypeDef CAN_host_send_gcode() { // Forward a Marlin Gcode via CAN (uses parser.command_letter, Gcode_no, parser.value_float()) + // Send a Gcode to the toolhead with parameters and values + // Gcode starts with extended frame which can send the Gcode with max 2 parameters and values. + // Extended frames are send to complete all parameters and values (max 2 per extended message). + // 1. Analyze Gcode command + // 2. Ignore gcodes that do not need to be forwarded + // 3. Send parameters and values + // char s[] = "G0 X123.45678 Y124.45678 Z125.45678 E126.45678 F127.45678\n"; + + HAL_StatusTypeDef status = HAL_OK; + + if (parser.command_letter != 'M') // Only forward Mxxx Gcode to toolhead + return HAL_OK; + + uint32_t Gcode_type = CAN_ID_GCODE_TYPE_M; // M-code, fixed for now + uint32_t Gcode_no = parser.codenum & CAN_ID_GCODE_NUMBER_MASK; + + if (Gcode_no == 109) // Convert M109(Hotend wait) to M104 (no wait) to keep the toolhead responsive + Gcode_no = 104; + + if ((Gcode_no == 501) || (Gcode_no == 502)) // M501=Restore settings, M502=Factory defaults + CAN_toolhead_setup_request = true; // Also update settings for the toolhead + + if ((Gcode_no != 104) && // Set hotend target temp + (Gcode_no != 106) && // Set cooling fan speed + (Gcode_no != 107) && // Cooling fan off + (Gcode_no != 115) && // Firmware info (testing) + (Gcode_no != 119) && // Endstop status (testing) + (Gcode_no != 150) && // Set NeoPixel values + //(Gcode_no != 108) && // Break and Continue + (Gcode_no != 280) && // Servo position + (Gcode_no != 306) && // MPC settings/tuning + (Gcode_no != 710) && // Control fan PWM + (Gcode_no != 997)) // Reboot + return HAL_OK; // Nothing to do + + uint32_t index; + uint32_t parameter_counter = 0; + char letters[] = "XYZEFPSABCHIJKLOQRTUVW"; // All possible parameters (22), defines scan order, no "D G M N", includes 'T' for autotune (M306 T) + static uint32_t parameters[8] = { 0 }; // Store found parameters, send max 7 parameters (send in pairs, so reserve 8), CodeA=1 (ASCII65), CodeE=5, CodeF=6, CodeX=88-64=24, CodeY=89-64=25, CodeZ=90-64=26 + static float values[8] = { 0 }; // Store found values, send max 7 parameters (send in pairs, so reserve 8) + + uint8_t CAN_tx_buffer[8]; // 8 bytes CAN data TX buffer + + /* + switch (parser.command_letter) // Filter/adjust Gcodes + { + case 'G': Gcode_type = CAN_ID_GCODE_TYPE_G; + switch (Gcode_no) + { + case 12: break; // No Nozzle cleaning support needed on toolhead + case 29: case 34: return HAL_OK; // No bedleveling/Z-syncing on toolhead + break; + } + break; + + case 'M': Gcode_type = CAN_ID_GCODE_TYPE_M; + switch (Gcode_no) + { // Save Prog mem: M112, M48, M85, M105, M114, M155, M500, M501, M502, M503, M226, M422 + case 109: Gcode_no = 104; break; // Replace M109 with M104 + case 112: Gcode_no = 104; break; // Don't shutdown board, should stop heating with "M104" + + case 20: case 21: case 22: case 23: case 24: case 25: case 26: case 524: case 540: case 928: + case 27: case 28: case 29: case 30: case 32: case 33: case 34: // No SD file commands + case 43: // No pin debug + case 48: // No repeatability test + case 85: // No inactivity shutdown + case 100: // No show free memory support + case 108: // Break and Continue + case 105: // No temperature reporting + case 114: // Don't report position + case 117: case 118: case 119: // Don't send strings + case 140: case 190: // Ignore bed temp commands + case 150: // Set NeoPixel values + case 154: // No auto position reporting + case 155: // No tempeature reporting + case 226: // Wait for pin state + case 240: // No camera support + case 250: // No LCD contrast support + case 260: case 261: // No I2C on toolhead + case 280: // Don't send servo angle, done via Servo.cpp already + case 290: // No baby stepping + case 300: // No tones + // case 303: // No PID autotune (done on TOOLHEAD) + case 304: // No bed PID settings + // case 306: // MPC autotune (done on TOOLHEAD) + case 350: case 351: // No live microstepping adjustment + case 380: case 381: // No solenoid support + case 401: case 402: // No probe deploy/stow, done via M280 servo angles + case 412: // Filament runout sensor done by MASTER + case 420: case 421: // No bed leveling state + case 423: // No X Twist Compensation + case 425: // No backlash compensation + case 500: case 501: case 502: case 503: case 504: case 910: // No EEPROM on toolhead, remove M50x commands to save Prog mem + case 510: case 511: case 512: // No locking of the machine + case 605: // No IDEX commands + case 810: case 811: case 812: case 813: case 814: case 815: case 816: case 817: case 818: case 819: + case 851: // + case 871: // No Probe temp config + case 876: // No handle prompt response + case 913: // No Set Hybrid Threshold Speed + case 914: // No TMC Bump Sensitivity + case 997: // No remote reset + case 998: // No ESP3D reset + return HAL_OK; // NO CAN MESSAGE + } + break; + + case 'T': Gcode_type = CAN_ID_GCODE_TYPE_T; + switch (Gcode_no) + { + case 0: case 1: + break; + } + break; + + case 'D': Gcode_type = CAN_ID_GCODE_TYPE_D; + switch (Gcode_no) + { + case 0: case 1: + break; + } + break; + default: return HAL_OK; // Invalid command, nothing to do + } + */ + + #if ENABLED(CAN_DEBUG) + SERIAL_ECHOPGM(">>> CAN Gcode to toolhead: "); + SERIAL_CHAR(parser.command_letter); + SERIAL_ECHO(Gcode_no); + #endif + + if (strlen(parser.command_ptr) > 4) // "M107\0", Only scan for parameters if the string is long enough + for (index = 0; index < sizeof(letters); index++) { // Scan parameters + if (parser.seen(letters[index])) { + parameters[parameter_counter] = (letters[index] - 64) & CAN_ID_PARAMETER_LETTER_MASK; // Store parameter letter, A=1, B=2... + + #if ENABLED(CAN_DEBUG) + SERIAL_CHAR(' ', letters[index]); + #endif + + if (parser.has_value()) { // Check if there is a value + values[parameter_counter++] = parser.value_float(); + + #if ENABLED(CAN_DEBUG) + if (values[parameter_counter - 1] == int(values[parameter_counter - 1])) + SERIAL_ECHO(i16tostr3left(values[parameter_counter - 1])); // Integer value + else + SERIAL_ECHO(p_float_t(values[parameter_counter - 1], 4)); // Float with 4 digits + #endif + } + else // No value for parameter + values[parameter_counter++] = NAN; // Not A Number, indicates no parameter value is present + } + + if (parameter_counter == 8) { // Max is 7 parameters + CAN_host_error_code |= CAN_ERROR_HOST_INVALID_GCODE; + parameter_counter--; + SERIAL_ECHOLNPGM("\nError: TOO MANY PARAMETERS (> 7): ", parser.command_ptr); + BUZZ(1, SOUND_ERROR); + break; + } + } + + #if ENABLED(CAN_DEBUG) + SERIAL_EOL(); + #endif + + parameters[parameter_counter] = 0; // Set next parameter to 0 (0=no parameter), send in pairs + index = 0; + float * fp = (float *)CAN_tx_buffer; // Points to TX buffer + + TxHeader.IdType = FDCAN_EXTENDED_ID; // Start Gcode with Extended ID, then send Standard ID messages messages if there are more than 2 parameters + + uint32_t deadline = millis() + CAN_HOST_MAX_WAIT_TIME; // Record message send start time + do { + TxHeader.DataLength = MIN(8, (parameter_counter - index) << 2) << FDCAN_HOST_DATALENGTH_OFFSET; // 4 bytes --> only 1 parameter, 8 bytes --> 2 parameters + + if (TxHeader.IdType == FDCAN_EXTENDED_ID) + TxHeader.Identifier = CAN_set_extended_id(Gcode_type, Gcode_no, parameters[index], parameters[index + 1], parameter_counter); + else { + CAN_host_FIFO_toggle_bit = !CAN_host_FIFO_toggle_bit; + TxHeader.Identifier = (CAN_host_FIFO_toggle_bit ? STDID_FIFO_TOGGLE_BIT : 0) | // Toggle bit + (parameters[index ] << CAN_ID_PARAMETER1_BIT_POS) | // Parameter 1 + (parameters[index + 1] << CAN_ID_PARAMETER2_BIT_POS); // Parameter 2 + } + + *fp++ = values[index++]; // Copy first parameter value to data, move pointer to next 4 bytes + *fp-- = values[index++]; // Copy 2nd parameter value to data, move pointer to beginning of data array for next round + + while ((HAL_FDCAN_GetTxFifoFreeLevel(&hCAN1) == 0) && PENDING(millis(), deadline)) { /* BLOCKING! Wait for empty TX Buffer */ } + + if (HAL_FDCAN_GetTxFifoFreeLevel(&hCAN1)) + status = HAL_FDCAN_AddMessageToTxFifoQ(&hCAN1, &TxHeader, CAN_tx_buffer); // Queue CAN message + else + CAN_host_error_code |= CAN_ERROR_HOST_TX_MSG_DROPPED; + + if (status != HAL_OK) return status; + + TxHeader.IdType = FDCAN_STANDARD_ID; // All following messages have standard ID for parameter values, 11 bits identifier + + } while (index < parameter_counter); + + return status; + +} // CAN_host_send_gcode + +void CAN_host_send_position() { // Send the X, Y, Z and E position to the TOOLHEAD + CAN_host_send_gcode_2params('G', 92, 'X', current_position.x, 'Y', current_position.y); // M92 X Y + CAN_host_send_gcode_2params('G', 92, 'Z', current_position.z, 'E', current_position.e); // M92 E Z +} + +// TODO: SETUP HARDWARE BASED ON CAN_RX, CAN_TX PINS +void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* canHandle) { // Called by HAL_FDCAN_Init + + if (canHandle->Instance == FDCAN1) { + +// Select the FDCAN clock source + __HAL_RCC_FDCAN_CONFIG(RCC_FDCANCLKSOURCE_HSE); // 25MHz, select external clock oscillator +// __HAL_RCC_FDCAN_CONFIG(RCC_FDCANCLKSOURCE_PLL); // 55MHz, select PLL +// __HAL_RCC_FDCAN_CONFIG(RCC_FDCANCLKSOURCE_PLL2); // 80MHz, select PLL2 + + if (__HAL_RCC_FDCAN_IS_CLK_DISABLED()) + __HAL_RCC_FDCAN_CLK_ENABLE(); // Enable FDCAN clock + + if (__HAL_RCC_GPIOB_IS_CLK_DISABLED()) // Should be enabled by Marlin already + __HAL_RCC_GPIOB_CLK_ENABLE(); // Enable GPIO B clock + // CAN1 GPIO Configuration + // PB8 ------> CAN1_RX + // PB9 ------> CAN1_TX + + GPIO_InitTypeDef GPIO_InitStruct = {0}; + GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9; // Pin PB8 and Pin PB9 + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF9_FDCAN1; // Alternate function 9 + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // Port B + + // Enabled the FDCAN interrupt handler + HAL_NVIC_SetPriority(FDCAN1_IT0_IRQn, 1, 1); // Set FDCAN interrupt priority + HAL_NVIC_EnableIRQ(FDCAN1_IT0_IRQn); // Enable FDCAN interrupt handler line 0 (default) + } +} + +HAL_StatusTypeDef CAN_host_stop() { + return HAL_FDCAN_Stop(&hCAN1); +} + +HAL_StatusTypeDef CAN_host_start() { + + HAL_StatusTypeDef status = HAL_OK; + + // Initialize TxHeader with constant values + TxHeader.FDFormat = FDCAN_CLASSIC_CAN; // FDCAN_CLASSIC_CAN / FDCAN_FD_CAN + TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; // FDCAN_NO_TX_EVENTS / FDCAN_STORE_TX_EVENTS + TxHeader.MessageMarker = 0; // 0-0xFF for tracking messages in FIFO buffer + TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE; // FDCAN_ESI_PASSIVE / FDCAN_ESI_ACTIVE (ACTIVE will notify transmission errors) + TxHeader.TxFrameType = FDCAN_DATA_FRAME; // FDCAN_DATA_FRAME / FDCAN_REMOTE_FRAME + TxHeader.IdType = FDCAN_STANDARD_ID; // FDCAN_STANDARD_ID / FDCAN_EXTENDED_ID + TxHeader.BitRateSwitch = FDCAN_BRS_OFF; // FDCAN_BRS_OFF / FDCAN_BRS_ON + TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; // FDCAN_NO_TX_EVENTS / FDCAN_STORE_TX_EVENTS (Do not store TX events) + + hCAN1.Instance = FDCAN1; // The FDCAN device to use + hCAN1.Init.FrameFormat = FDCAN_FRAME_CLASSIC; // FDCAN_FRAME_CLASSIC / FDCAN_FRAME_FD_BRS / FDCAN_FRAME_FD_NO_BRS (Bit Rate Switching) + hCAN1.Init.Mode = FDCAN_MODE_NORMAL; // FDCAN_MODE_NORMAL / FDCAN_MODE_EXTERNAL_LOOPBACK / FDCAN_MODE_INTERNAL_LOOPBACK / FDCAN_MODE_BUS_MONITORING / FDCAN_MODE_RESTRICTED_OPERATION + hCAN1.Init.AutoRetransmission = DISABLE; // Auto Retransmission of message if error occured, warning: can cause lockup + hCAN1.Init.TransmitPause = DISABLE; // Transmit Pause to allow for other device to send message + hCAN1.Init.ProtocolException = DISABLE; // ProtocolException handling + + // FDCAN baud rate = FDCAN Clock / Prescaler / (SJW + TSG1 + TSG2) + // Sample-Point from 50 to 90% (87.5 % is the preferred value used by CANopen and DeviceNet, 75% is the ARINC 825 default) + // STM32H7xx @ 550MHz, FDCAN clock: HSE=25MHz, PLL=55Mhz, PLL2=80MHz + // Baud rate = 25M / 1 / 1 / (1 + 21 + 3) = 1M baud (Sample point = 22/25 = 88%) + // Baud rate = 25M / 1 / 2 / (1 + 21 + 3) = 500k baud + // HSE 25MHz: ( 1 1 21 3) --> 1M baud; ( 2 1 21 3) --> 500K baud; ( 4 1 21 3) --> 250K baud; + // PLL 55MHz: (11 1 3 1) --> 1M baud; ( 22 1 3 1) --> 500K baud; (44 1 3 1) --> 250K baud; + // PLL2 80MHz: ( 1 1 69 10) ( 2 1 34 5) ( 4 1 16 3) --> 1M baud + // PLL2 80MHz: ( 2 1 69 10) ( 4 1 34 5) ( 8 1 16 3) --> 500K baud + // PLL2 80MHz: ( 4 1 69 10) ( 8 1 34 5) (16 1 16 3) --> 250K baud + + hCAN1.Init.NominalPrescaler = 1; // Arbitration/data clock prescaler/divider (1-512) + hCAN1.Init.NominalSyncJumpWidth = 1; // Arbitration/data Sync Jump Width (1-128 SJW), should be 1 + hCAN1.Init.NominalTimeSeg1 = 21; // Arbitration/data period 1 (2-256) + hCAN1.Init.NominalTimeSeg2 = 3; // Arbitration/data period 2 (2-128) + +/* No bitrate switching, not used: + hCAN1.Init.DataPrescaler = 1; // Arbitration/data clock prescaler/divider (1-32) + hCAN1.Init.DataSyncJumpWidth = 1; // Arbitration/data Sync Jump Width (1-16 SJW), should be 1 + hCAN1.Init.DataTimeSeg1 = 21; // Arbitration/data period 1 (2-32) + hCAN1.Init.DataTimeSeg2 = 3; // Arbitration/data period 2 (1-16) +*/ + hCAN1.Init.MessageRAMOffset = 0; // Only using 1 FDCAN device, so offset is 0. FDCAN_MESSAGE_RAM_SIZE: 2560 Words, 10KBytes + hCAN1.Init.TxEventsNbr = 0; // 0-32 + hCAN1.Init.TxBuffersNbr = 0; // 0-32 + hCAN1.Init.TxFifoQueueElmtsNbr = FDCAN_HOST_TX_FIFO_DEPTH; // 0-32 + hCAN1.Init.TxElmtSize = FDCAN_DATA_BYTES_8; + + hCAN1.Init.RxBufferSize = FDCAN_DATA_BYTES_8; // FDCAN_data_field_size + hCAN1.Init.RxBuffersNbr = 0; // 0-64 + hCAN1.Init.RxFifo0ElmtsNbr = FDCAN_HOST_RX_FIFO_DEPTH; // 0-64 + hCAN1.Init.RxFifo0ElmtSize = FDCAN_DATA_BYTES_8; + hCAN1.Init.RxFifo1ElmtsNbr = FDCAN_HOST_RX_FIFO_DEPTH; // 0-64 + hCAN1.Init.RxFifo1ElmtSize = FDCAN_DATA_BYTES_8; + + hCAN1.Init.StdFiltersNbr = 2; // Number of standard frame filters + hCAN1.Init.ExtFiltersNbr = 2; // Number of extended frame filters + hCAN1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; // Queue mode: FDCAN_TX_FIFO_OPERATION / FDCAN_TX_QUEUE_OPERATION + + status = HAL_FDCAN_Init(&hCAN1); // Calls HAL_FDCAN_MspInit + if (status != HAL_OK) return status; + + // FDCAN Filter configuration, toggle reception between FIFO0 and FIFO1 (not really needed for this FDCAN implementation) + FDCAN_FilterTypeDef sFilterConfig; // Configure RX message filter + + sFilterConfig.IdType = FDCAN_EXTENDED_ID; // Filter extended ID messages to FIFO1 if higest ID bit is set + sFilterConfig.FilterIndex = 0; // Exteneded filter ID 0 + sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE + sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1; // Filter to FIFO1 + sFilterConfig.FilterID1 = 0x10000000; // Range: 0 - 0x1FFF FFFF, 0=don'tcare + sFilterConfig.FilterID2 = 0x10000000; // Range: 0 - 0x1FFF FFFF, 0=don'tcare + status = HAL_FDCAN_ConfigFilter(&hCAN1, &sFilterConfig); + if (status != HAL_OK) return status; + +//sFilterConfig.IdType = FDCAN_EXTENDED_ID; // Filter all remaining extended ID messages to FIFO0 + sFilterConfig.FilterIndex = 1; // Exteneded filter ID 1 +//sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE + sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; // Remaining messages go to FIFO0 + sFilterConfig.FilterID1 = 0; // Range: 0 - 0x1FFF FFFF, 0=don'tcare + sFilterConfig.FilterID2 = 0; // Range: 0 - 0x1FFF FFFF, 0=don'tcare + HAL_FDCAN_ConfigFilter(&hCAN1, &sFilterConfig); + if (status != HAL_OK) return status; + + sFilterConfig.IdType = FDCAN_STANDARD_ID; // Filter to FIFO1 if higest standard ID bit is set + sFilterConfig.FilterIndex = 0; // Standard filter ID 0 + sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE + sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; + sFilterConfig.FilterID1 = 0b10000000000; // Range: 0 - 0x7FF, 0=don't care + sFilterConfig.FilterID2 = 0b10000000000; // Range: 0 - 0x7FF, 0=don't care + status = HAL_FDCAN_ConfigFilter(&hCAN1, &sFilterConfig); + if (status != HAL_OK) return status; + +//sFilterConfig.IdType = FDCAN_STANDARD_ID; // Filter all remaining standard ID messages to FIFO0 + sFilterConfig.FilterIndex = 1; // Standard filter ID 1 +//sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE + sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1; // Remaining to FIFO0 + sFilterConfig.FilterID1 = 0; // Range: 0 - 0x7FF, 0=don't care + sFilterConfig.FilterID2 = 0; // Range: 0 - 0x7FF, 0=don't care + status = HAL_FDCAN_ConfigFilter(&hCAN1, &sFilterConfig); + if (status != HAL_OK) return status; + + // status = HAL_FDCAN_ConfigGlobalFilter(&hfdcan, FDCAN_REJECT, FDCAN_REJECT, FDCAN_REJECT_REMOTE, FDCAN_REJECT_REMOTE); + //if (status != HAL_OK) return status; + + status = HAL_FDCAN_ActivateNotification(&hCAN1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0); // Calls HAL_FDCAN_RxFifo0MsgPendingCallback + if (status != HAL_OK) return status; + + status = HAL_FDCAN_ActivateNotification(&hCAN1, FDCAN_IT_RX_FIFO1_NEW_MESSAGE, 0); // Calls HAL_FDCAN_RxFifo1MsgPendingCallback + if (status != HAL_OK) return status; + + HAL_FDCAN_ActivateNotification(&hCAN1, FDCAN_IT_RX_FIFO0_MESSAGE_LOST, 0); // Calls HAL_FDCAN_ErrorCallback + if (status != HAL_OK) return status; + + status = HAL_FDCAN_ActivateNotification(&hCAN1, FDCAN_IT_RX_FIFO1_MESSAGE_LOST, 0); // Calls HAL_FDCAN_ErrorCallback + if (status != HAL_OK) return status; + + status = HAL_FDCAN_ActivateNotification(&hCAN1, FDCAN_IT_ERROR_WARNING, 0); // Calls HAL_FDCAN_ErrorCallback + if (status != HAL_OK) return status; + + status = HAL_FDCAN_Start(&hCAN1); // Start the FDCAN device + if (status != HAL_OK) return status; + + #if ENABLED(CAN_DEBUG) + SERIAL_ECHOLNPGM("Voltage Scaling: VOS", (PWR->CSR1 & PWR_CSR1_ACTVOS_Msk) >> PWR_CSR1_ACTVOS_Pos); + SERIAL_ECHOLNPGM("FDCAN Peripheral Clock : ", HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_FDCAN) / 1000000, "MHz"); + SERIAL_ECHOLNPGM("FDCAN Timing. Prescaler: ", hCAN1.Init.NominalPrescaler, " SJW: ", hCAN1.Init.NominalSyncJumpWidth, " SEG1: ", hCAN1.Init.NominalTimeSeg1, " SEG2: ", hCAN1.Init.NominalTimeSeg2); + uint32_t baudrate = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_FDCAN) / hCAN1.Init.NominalPrescaler / (hCAN1.Init.NominalSyncJumpWidth + hCAN1.Init.NominalTimeSeg1 + hCAN1.Init.NominalTimeSeg2); + SERIAL_ECHOLNPGM("FDCAN BAUDRATE: ", baudrate); + + if (baudrate != CAN_BAUDRATE) { + SERIAL_ECHOLNPGM("Error: Incorrect FDCAN baudrate generated: ", baudrate, " FDCAN_BAUDRATE=", CAN_BAUDRATE); + CAN_host_error_code |= CAN_ERROR_HOST_INVALID_BAUDRATE; + } + #endif + + #ifdef FDCAN_LED_PIN + pinMode(FDCAN_LED_PIN, OUTPUT); + #endif + status = CAN_host_send_gcode_2params('M', 997, 0, 0, 0, 0); // M997, reset toolhead at host startup + + return status; +} // CAN_host_start() + +void FDCAN_host_read_message(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo) { // ISR! FIFO 0/1 message interrupt handler + + FDCAN_RxHeaderTypeDef RxHeader; + uint8_t CAN_RX_buffer_FIFO[8]; // CAN message buffer + + if (HAL_FDCAN_GetRxFifoFillLevel(hfdcan, RxFifo) && + (HAL_FDCAN_GetRxMessage(hfdcan, RxFifo, &RxHeader, CAN_RX_buffer_FIFO) == HAL_OK)) { // Get message from CAN_RX_FIFO0 + + if ((RxHeader.Identifier & CAN_ID_IO_MASK) != CAN_io_state) { // First handle time critical virtual IO update + CAN_io_state = (RxHeader.Identifier & CAN_ID_IO_MASK); + endstops.update(); + } + + if (RxHeader.Identifier & CAN_ID_STRING_MESSAGE_MASK) { // Toolhead sent a string message + char * CAN_RX_p = (char *)CAN_RX_buffer_FIFO; + for (uint32_t i = 0; i < (RxHeader.DataLength >> FDCAN_HOST_DATALENGTH_OFFSET); i++) { + string_message[string_message_index++ % CAN_HOST_MAX_STRING_MSG_LENGTH] = CAN_RX_p[i]; // Copy message to global buffer + + if (CAN_RX_p[i] == '\n') { + string_message_complete = true; // String is complete, idle task can show the string + string_message[string_message_index % CAN_HOST_MAX_STRING_MSG_LENGTH] = 0; // Close string with \0 + } + } + } + else if (RxHeader.DataLength == FDCAN_DLC_BYTES_4) { // Only 1 record, so it's a temperature update (DLC = Data Length Code == 4 bytes) + float *fp = (float *)CAN_RX_buffer_FIFO; + thermalManager.temp_hotend[0].celsius = *fp; // Set E0 hotend temperature from received message + CAN_next_temp_report_time = millis() + CAN_HOST_E0_TEMP_UPDATE_WATCHDOG_TIME; // A temp update must be received within this window + first_E0_error = true; // Reset error status + } + + if (RxHeader.Identifier & CAN_ID_REQUEST_TIME_SYNC_MASK) { // Toolhead signals request for time stamp + time_sync_request_time = micros(); // Record the time sync request receive time + CAN_time_sync_request = true; + } + + CAN_toolhead_setup_request = (RxHeader.Identifier & CAN_ID_REQUEST_SETUP_MASK) > 0; // Toolhead requests setup configuration + + CAN_toolhead_error = (RxHeader.Identifier & CAN_ID_ERROR_MASK) > 0; // Toolhead signals an error + } +} + +void FDCAN1_IT0_IRQHandler() { // ISR! FDCAN line 0 interrupt handler (overrides weak function) + + #ifdef FDCAN_LED_PIN + pinMode(FDCAN_LED_PIN, OUTPUT); + TOGGLE(FDCAN_LED_PIN); + #endif + + HAL_FDCAN_IRQHandler(&hCAN1); // Forward the interrupt call to the FDCAN interrupt handler for callbacks + +} + +void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) { // ISR! CAN FIFO0 new message interrupt handler + + if (RxFifo0ITs & FDCAN_IT_RX_FIFO0_MESSAGE_LOST) { // Check the error status first, it might be cleared by reading a message + CAN_host_error_code |= CAN_ERROR_HOST_RX_FIFO_OVERFLOW; + __HAL_FDCAN_CLEAR_FLAG(hfdcan, FDCAN_IT_RX_FIFO0_MESSAGE_LOST); // Clear interrupt flag + } + + if (RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) + FDCAN_host_read_message(hfdcan, FDCAN_RX_FIFO0); // Forward call + +} + +void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs) { // ISR! CAN FIFO1 new message interrupt handler + + if (RxFifo1ITs & FDCAN_IT_RX_FIFO1_MESSAGE_LOST) { // Check the error status first, it might be cleared by reading a message + CAN_host_error_code |= CAN_ERROR_HOST_RX_FIFO_OVERFLOW; + __HAL_FDCAN_CLEAR_FLAG(hfdcan, FDCAN_IT_RX_FIFO1_MESSAGE_LOST); // Clear interrupt flag + } + + if ((RxFifo1ITs & FDCAN_IT_RX_FIFO1_NEW_MESSAGE)) + FDCAN_host_read_message(hfdcan, FDCAN_RX_FIFO1); // Forward call +} + +void HAL_FDCAN_ErrorStatusCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ErrorStatusITs) { // ISR! + + // FDCAN_IR_EP | FDCAN_IR_EW | FDCAN_IR_BO ERROR PASSIVE, WARNINGS, BUS OFF + HAL_FDCAN_error_code = hfdcan->ErrorCode; // Store the received FDCAN error code +} + +void HAL_FDCAN_ErrorCallback(FDCAN_HandleTypeDef *hfdcan) { // ISR! FDCAN error interrupt handler +// FDCAN_IR_ELO | FDCAN_IR_WDI | FDCAN_IR_PEA | FDCAN_IR_PED | FDCAN_IR_ARA LOGGING OVERFLOW, WATCHDOG INTERRUPT, PROTOCOL ERRORS, ACCESS RESERVED AREA + HAL_FDCAN_error_code = hfdcan->ErrorCode; // Store the received FDCAN error code +} + +#endif // CAN_HOST && STM32H7xx diff --git a/Marlin/src/HAL/STM32/FDCAN_toolhead.cpp b/Marlin/src/HAL/STM32/FDCAN_toolhead.cpp new file mode 100644 index 000000000000..ede60ed4fa54 --- /dev/null +++ b/Marlin/src/HAL/STM32/FDCAN_toolhead.cpp @@ -0,0 +1,732 @@ +/** + * 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 . + * + */ + +/** + * Contributor Notes: + * =============== + * In \Users\\.platformio\packages\framework-arduinoststm32@4.20600.231001\libraries\SrcWrapper\src\HardwareTimer.cpp + * Add "__weak" in front of "void TIM16_IRQHandler(void)" to be able to override the function and redirect the interrupt + */ + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(CAN_TOOLHEAD) +// TODO: +// 1. Convert into class +// 2. Pickup correct CAN device from CAN_RX_PIN/CAN_TX_PIN +// 3. Calculate baudrate and data sampling timing from clock frequencies defined by CAN_BAUDRATE + +#include "../platforms.h" +#include "../../gcode/gcode.h" +#include "../../gcode/parser.h" +#include "../../gcode/queue.h" +#include "../../module/temperature.h" +#include "../../libs/numtostr.h" +#include "../../inc/MarlinConfig.h" +#include "../../feature/controllerfan.h" +#include "../../core/serial.h" + +#include "../shared/CAN_HOST.h" + +extern "C" void TIM16_IRQHandler(void); // Override weak functions +extern "C" void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs); +extern "C" void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs); + +#ifndef CAN_LED_PIN + #define CAN_LED_PIN LED_PIN +#endif + +#define CAN_DATALENGTH_OFFSET 16 // Used for CAN TxHeader DataLength record + +#ifndef CAN_BAUDRATE + #define CAN_BAUDRATE 1000000 +#endif + +#define FDCAN_RX_FIFO0_MASK (FDCAN_IR_RF0L | FDCAN_IR_RF0F | FDCAN_IR_RF0N) // FIFO0: Message lost | FIFO full | New message +#define FDCAN_RX_FIFO1_MASK (FDCAN_IR_RF1L | FDCAN_IR_RF1F | FDCAN_IR_RF1N) // FIFO1: Message lost | FIFO full | New message +#define FDCAN_RX_FIFO_MASK (FDCAN_RX_FIFO0_MASK | FDCAN_RX_FIFO1_MASK) // FIFO : Message lost | FIFO full | New message +#define HAL_TIM_FLAG_ALL_MASK (TIM_FLAG_CC1 | TIM_FLAG_CC2 | TIM_FLAG_CC3 | TIM_FLAG_CC4 | TIM_FLAG_UPDATE | TIM_FLAG_BREAK | TIM_FLAG_BREAK2 | TIM_FLAG_TRIGGER | TIM_FLAG_COM) +#define M_GCODE(A) ((CAN_ID_GCODE_TYPE_M << (CAN_ID_GCODE_TYPE_BIT_POS - CAN_ID_GCODE_NUMBER_BIT_POS)) + A) + +#define CAN_FIFO_DEPTH 3 // 3 FIFO buffers per FIFO on STM32G0xx +#define CAN_MAX_WAIT_TIME 25 // Amount of ms to wait for resources (TX buffers/Marlin command buffers) +#define CAN_TIMESTAMP_NO 7777 // M7777 is used to handle timesync messages +#define CAN_TOOLHEAD_CONFIGURATION_COMPLETE 7778 // Signal the configuration is complete +#define CAN_TEMPERATURE_REPORT_INTERVAL 950 // Temperature report interval in ms (faster than once a second) +#define CAN_MAX_STRING_MESSAGE_LENGTH 128 // Max string length to send to the host +#define CAN_EXTENDED_ID_MARKER_MASK (1 << 31) // Extended ID marker in CAN_BUFFER identifier +#define CAN_QUEUE_DEPTH 16 // CAN queue depth + +FDCAN_HandleTypeDef hCAN1; // The global CAN handle + +struct CAN_MSG_BUFFER { // Struct to hold a CAN message + uint32_t identifier; + float data[2]; + uint32_t receive_time; // Used for time sync +}; + +SString CAN_string_buffer; // String buffer to hold outgoing string messages +char gcode_type[4] = { 'D', 'G', 'M', 'T' }; // The 4 Gcode types +bool CAN_toolhead_not_configured = true; // Track if the host sent the required toolhead configuration +uint32_t CAN_toolhead_error_code = 0; // Signals an CAN error occured, report to host +uint32_t CAN_previous_error_code = 0; // Remember last error code so changes can be detected +uint32_t CAN_next_temp_report_time = 0; // The next temperature report time +uint32_t CAN_send_next_string_part_time = 0; // Send the next part of a string message +volatile bool CAN_FIFO_toggle_bit = 0; // FIFO toggle bit for receiver filtering to FIFO0 and FIFO1 +bool CAN_request_time_sync = false; // Request a timestamp for NTP time sync + +int CAN_NTP_time_offset = 0; // Time offset in micro seconds in relation to the host time +volatile uint32_t NTP[5] = { 0 }; // NTP style timestamps for time sync +float CAN_NTP_clock_drift = 0; // Time sync calculated clock drift between host and toolhead + +uint32_t CAN_next_led_flash_time = 0; // Error LED flashing +uint32_t CAN_next_error_message_time = 0; // Controls error message repeat frequency + +CAN_MSG_BUFFER CAN_QUEUE[CAN_QUEUE_DEPTH] = { 0 }; // Circular CAN message queue +volatile uint32_t CAN_queue_head = 0; // Queue head index +volatile uint32_t CAN_queue_tail = 0; // Queue tail index + +// Function to calculate the CAN message ID holding various bits of information +uint32_t CAN_get_virtual_IO() { + + CAN_FIFO_toggle_bit = !CAN_FIFO_toggle_bit; // Toggle FIFO bit for receiver filtering to FIFO0 and FIFO1 + + return ( + + #ifdef Z_MIN_PROBE_PIN + (READ(Z_MIN_PROBE_PIN) << CAN_ID_PROBE_BIT_POS) | // Report probe status + #endif + #ifdef X_MIN_PIN + (READ(X_MIN_PIN) << CAN_ID_X_ENDSTOP_BIT_POS) | // Report X-min status + #endif + #ifdef Y_MIN_PIN + (READ(Y_MIN_PIN) << CAN_ID_Y_ENDSTOP_BIT_POS) | // Report Y-min status + #endif + #ifdef Z_MIN_PIN + (READ(Z_MIN_PIN) << CAN_ID_Z_ENDSTOP_BIT_POS) | // Report Z-min status + #endif + #ifdef FILAMENT_RUNOUT_PIN + (READ(FILAMENT_RUNOUT_PIN) << CAN_ID_FILAMENT_BIT_POS) | // Report filament detector status + #endif + + (CAN_FIFO_toggle_bit ? STDID_FIFO_TOGGLE_BIT : 0) | // Add FIFO toggle bit + (CAN_toolhead_not_configured << CAN_ID_REQUEST_SETUP_BIT_POS) | // Request toolhead setup configuration + ((!!CAN_toolhead_error_code) << CAN_ID_ERROR_BIT_POS) // Report error (if any) + ); +} + +// Convert a float to a string, formatted as used in Gcode commands, store the result at codeP +void floatToString(float value, char *codeP) { + + if (value < 0.0) { + *codeP++ = '-'; // Add minus sign to float + value = -value; // Make value positive for further processing + } + + uint32_t i1 = int(value); // Get integer part of float + itoa(i1, codeP, 10); + codeP += strlen(codeP); // Adjust pointer + value = (value - i1); // Get fractional part of float + + uint32_t j = 0; + for (int i = 0; i < 5; i++) { // Calculate 6 digits, 1 extra digit for rounding + value *= 10.0; + i1 = int(value); + j = (10 * j) + i1; + value = value - i1; + } + + if (value >= 0.5) + j++; // Round up + + if (j) { + *codeP++ = '.'; + if (j < 10000) { + *codeP++ = '0'; + if (j < 1000) { + *codeP++ = '0'; + if (j < 100) { + *codeP++ = '0'; + if (j < 10) + *codeP++ = '0'; + } + } + } + + while (!(j % 10)) // Remove trailing zeros, "120" --> "12" + j = j / 10; + + itoa(j, codeP, 10); + } +} + +// Add received parameters to gcode string at codeP (used in ISR!) +void CAN_add_gcode_parameters(uint32_t parameter, float value, char *codeP) { + + *codeP++ = char(parameter + 64); // Add Gcode parameter letter, e.g., 'X' + + if (!isnan(value)) // No value for parameter if value is "Not A Number" + floatToString(value, codeP); + else + *codeP = '\0'; +} + +// Process a received message (offline in the idle handler) +void process_can_queue() { + static char CAN_gcode_buffer[MAX_CMD_SIZE]; + static uint32_t parameter_counter = 0; + + uint32_t identifier = CAN_QUEUE[CAN_queue_tail].identifier; + static char *codeP = CAN_gcode_buffer; // Put Gcode pointer to start of CAN_gcode_buffer; + bool enqueue = true; + + // Receiving new Gcode + if (identifier & CAN_EXTENDED_ID_MARKER_MASK) { + + uint32_t gcode = (identifier >> CAN_ID_GCODE_NUMBER_BIT_POS) & CAN_ID_GCODE_MASK; + + // M997, restart the toolhead unconditionally + if (gcode == M_GCODE(997)) { + SERIAL_ECHOLNPGM("\n>>> Host requested a toolhead restart (M997)..."); + delay(25); + flashFirmware(0); + while (1); + } + + // The host indicatds that the configuration is complete + if (gcode == M_GCODE(CAN_TOOLHEAD_CONFIGURATION_COMPLETE)) { + CAN_toolhead_not_configured = false; + MString<60> buffer("FIRMWARE ", __DATE__, " ", __TIME__, " Thermistor=", TEMP_SENSOR_0); + CAN_toolhead_send_string(buffer); + enqueue = false; + } + + // M115 triggers the time sync process + if (gcode == M_GCODE(115)) // TESTING... + CAN_request_time_sync = true; // Send time sync request in idle task and wait for response + + // M, time sync response message + if (gcode == M_GCODE(CAN_TIMESTAMP_NO)) { + // Data contains 2 timestamps in uint32_t format + uint32_t *uint32p = (uint32_t*)CAN_QUEUE[CAN_queue_tail].data; + NTP[1] = *uint32p++; + NTP[2] = *uint32p; + NTP[3] = CAN_QUEUE[CAN_queue_tail].receive_time; // Record time sync response message receive time + + enqueue = false; // Timestamps were stored, nothing else to do + } + + // New Gcode started, so previous Gcode must be complete (all parameters received) + if (parameter_counter) + CAN_toolhead_error_code |= CAN_ERROR_TOOLHEAD_INCOMPLETE_GCODE_RECEIVED; + + parameter_counter = (identifier >> CAN_ID_PARAMETER_COUNT_BIT_POS) & CAN_ID_PARAMETER_COUNT_MASK; // Get Gcode parameter count + + *codeP++ = gcode_type[(identifier >> CAN_ID_GCODE_TYPE_BIT_POS) & CAN_ID_GCODE_TYPE_MASK]; // Add Gcode letter e.g., "G" + itoa((identifier >> CAN_ID_GCODE_NUMBER_BIT_POS) & CAN_ID_GCODE_NUMBER_MASK, codeP, 10); // Add Gcode number e.g., G"92" + } + + // Add parameters (if present) to the received Gcode + if (parameter_counter && ((identifier >> CAN_ID_PARAMETER1_BIT_POS) & CAN_ID_PARAMETER_LETTER_MASK)) { // Get 1st parameter, make sure it's not empty. + codeP += strlen(codeP); // Adjust the pointer to the already present data in the buffer + CAN_add_gcode_parameters(identifier & CAN_ID_PARAMETER_LETTER_MASK, CAN_QUEUE[CAN_queue_tail].data[0], codeP); + parameter_counter--; + + // Add 2nd parameter if 2 values were provided + if (parameter_counter && ((identifier >> CAN_ID_PARAMETER2_BIT_POS) & CAN_ID_PARAMETER_LETTER_MASK)) { // Get 2nd parameter, make sure it's not empty. + codeP += strlen(codeP); // Adjust the pointer to the already present data in the buffer + CAN_add_gcode_parameters((identifier >> CAN_ID_PARAMETER2_BIT_POS) & CAN_ID_PARAMETER_LETTER_MASK, CAN_QUEUE[CAN_queue_tail].data[1], codeP); + parameter_counter--; + } + } + + if (!parameter_counter) { // Gcode is complete, including all parameters, process the Gcode + + codeP = CAN_gcode_buffer; // Move pointer to start of string + + if (enqueue) { + // queue.enqueue_one returns TRUE if the command was queued, FALSE if the Marlin cmd buffer was full + if (queue.enqueue_one(CAN_gcode_buffer)) { // Increase tail only when commands was handled + CAN_queue_tail = (CAN_queue_tail + 1) % CAN_QUEUE_DEPTH; // Advance tail only is command was enqueued + #ifdef CAN_DEBUG + SERIAL_ECHOLNPGM(";", millis(), " ", CAN_gcode_buffer); + #endif + } + } + else + CAN_queue_tail = (CAN_queue_tail + 1) % CAN_QUEUE_DEPTH; // Always advance tail + } + else + CAN_queue_tail = (CAN_queue_tail + 1) % CAN_QUEUE_DEPTH; + +} // process_can_queue + +HAL_StatusTypeDef CAN_receive_msg(uint32_t FIFO) { // ISR! Process received CAN message in interrupt handler + + if (((CAN_queue_head + 1) % CAN_QUEUE_DEPTH) != CAN_queue_tail) { // Check if a buffer is available + + FDCAN_RxHeaderTypeDef CAN_rx_header; + CAN_QUEUE[CAN_queue_head].receive_time = micros(); // Save receiver timestamp for time sync + HAL_FDCAN_GetRxMessage(&hCAN1, FIFO, &CAN_rx_header, (uint8_t*)CAN_QUEUE[CAN_queue_head].data); + + if (CAN_rx_header.IdType == FDCAN_EXTENDED_ID) // Mark extended ID message (new Gcode start) + CAN_QUEUE[CAN_queue_head].identifier = CAN_EXTENDED_ID_MARKER_MASK + CAN_rx_header.Identifier; + else + CAN_QUEUE[CAN_queue_head].identifier = CAN_rx_header.Identifier; // Standard ID message (Gcode parameters) + + CAN_queue_head = (CAN_queue_head + 1) % CAN_QUEUE_DEPTH; + } + else + CAN_toolhead_error_code |= CAN_ERROR_TOOLHEAD_RX_FIFO_OVERFLOW; + + return HAL_OK; + +} // CAN_receive_msg + +// NOTE: TIM16 is also used for the stepper! +void TIM16_IRQHandler(void) { // ISR! Combined TIM16 and CAN interrupt handler (override weak function) + + // Check if the timer caused the interrupt + if (TIM16->SR & HAL_TIM_FLAG_ALL_MASK) +// HardwareTimer_Handle[TIMER16_INDEX]->handle.Instance->SR & HAL_TIM_FLAG_ALL_MASK) + HAL_TIM_IRQHandler(&HardwareTimer_Handle[TIMER16_INDEX]->handle); + // Forward call to timer interrupt handler + // + // OR + // + // Forward to Marlin stepper ISR directly? + + // Call the FDCAN interrupt handler + //if (hCAN1.Instance->IR & (FDCAN_IR_RF0N | FDCAN_IR_RF0L | FDCAN_IR_RF1N | FDCAN_IR_RF1L)) + // HAL_FDCAN_IRQHandler(&hCAN1); // Forward call to FDCAN interrupt handler, calls HAL_FDCAN_RxFifo0Callback and HAL_FDCAN_RxFifo1Callback + // + // OR + // + // Call the required callbacks directly, faster but limited error reporting + if (hCAN1.Instance->IR & FDCAN_IR_RF0N) { // New FIFO0 CAN message + __HAL_FDCAN_CLEAR_FLAG(&hCAN1, FDCAN_IR_RF0N); + CAN_receive_msg(FDCAN_RX_FIFO0); + } + + if (hCAN1.Instance->IR & FDCAN_IR_RF1N) { // New FIFO1 CAN message + __HAL_FDCAN_CLEAR_FLAG(&hCAN1, FDCAN_IR_RF1N); + CAN_receive_msg(FDCAN_RX_FIFO1); + } + + // Check for lost CAN messages + if (hCAN1.Instance->IR & (FDCAN_IR_RF0L | FDCAN_IR_RF1L)) { + CAN_toolhead_error_code |= CAN_ERROR_TOOLHEAD_RX_FIFO_OVERFLOW; + __HAL_FDCAN_CLEAR_FLAG(&hCAN1, FDCAN_IR_RF0L | FDCAN_IR_RF1L); + } + +} // TIM16_IRQHandler + +void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) { // ISR! Override "weak" function + + if (RxFifo0ITs & FDCAN_IT_RX_FIFO0_MESSAGE_LOST) + CAN_toolhead_error_code |= CAN_ERROR_TOOLHEAD_RX_FIFO_OVERFLOW; + + if (RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) + CAN_receive_msg(FDCAN_RX_FIFO0); + +} + +void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs) { // ISR! Override "weak" function + + if (RxFifo1ITs & FDCAN_IT_RX_FIFO1_MESSAGE_LOST) + __HAL_FDCAN_CLEAR_FLAG(hfdcan, FDCAN_IT_RX_FIFO1_MESSAGE_LOST); // Clear FIFO1 message lost interrupt flag + + if (RxFifo1ITs & FDCAN_IT_RX_FIFO1_NEW_MESSAGE) + CAN_receive_msg(FDCAN_RX_FIFO1); + +} + +void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* fdcanHandle) { // Called automatically by FDCAN_init(), configure GPIO for CAN, enable interrupts + + RCC_PeriphCLKInitTypeDef PeriphClkInit = { 0 }; + + PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_FDCAN; +//PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_HSE; // 8MHz external crystal +//PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL; // 64MHz + PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_PCLK1; // 64MHz + + HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit); + + // STM32G0B1 and STM32G0 C1 only + // CAN1/2 clock enable (one clock for both CAN devices) + if (__HAL_RCC_FDCAN_IS_CLK_DISABLED()) + __HAL_RCC_FDCAN_CLK_ENABLE(); + + if (__HAL_RCC_GPIOB_IS_CLK_DISABLED()) + __HAL_RCC_GPIOB_CLK_ENABLE(); // ENABLE GPIO B CLOCK + // FDCAN2 GPIO Configuration + // PB0 AF3 ------> FDCAN2_RX + // PB1 AF3 ------> FDCAN2_TX + + GPIO_InitTypeDef GPIO_InitStruct = { 0 }; + GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1; // Pin PB0 and Pin PB1 + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // Alternate function + GPIO_InitStruct.Pull = GPIO_NOPULL; // No pullup or pulldown + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // High frequency device + GPIO_InitStruct.Alternate = GPIO_AF3_FDCAN2; // Alternate function 3 + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // Port B + + // Enable CAN interrupts + HAL_NVIC_SetPriority(TIM16_FDCAN_IT0_IRQn, 1, 1); // Set interrupt priority + HAL_NVIC_EnableIRQ(TIM16_FDCAN_IT0_IRQn); // Enable interrupt handler + +} // HAL_FDCAN_MspInit + +HAL_StatusTypeDef CAN_toolhead_start(void) { // Start the CAN device + + HAL_StatusTypeDef status = HAL_OK; + // CAN baud rate = Device Clock Frequency / Clock Divider / Prescaler / (SJW + TSG1 + TSG2) + // Sample-Point from 50 to 90% (87.5 % is the preferred value used by CANopen and DeviceNet, 75% is the ARINC 825 default) + // Baud rate = 64M / 1 / 4 / (1 + 13 + 2) = 1M Bit sample point = (1 + 13) / (1 + 13 + 2) = 87.5% + // Baud rate = 64M / 1 / 8 / (1 + 13 + 2) = 500K Bit sample point = (1 + 13) / (1 + 13 + 2) = 87.5% + // Baud rate = 64M / 1 / 16 / (1 + 13 + 2) = 250K Bit sample point = (1 + 13) / (1 + 13 + 2) = 87.5% + + hCAN1.Instance = FDCAN2; // The FDCAN device used + hCAN1.Init.ClockDivider = FDCAN_CLOCK_DIV1; // Clock divider 1 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 + hCAN1.Init.FrameFormat = FDCAN_FRAME_CLASSIC; // FDCAN_FRAME_CLASSIC / FDCAN_FRAME_FD_BRS / FDCAN_FRAME_FD_NO_BRS (Bit Rate Switching) + hCAN1.Init.Mode = FDCAN_MODE_NORMAL; // FDCAN_MODE_NORMAL / FDCAN_MODE_EXTERNAL_LOOPBACK / FDCAN_MODE_INTERNAL_LOOPBACK / FDCAN_MODE_BUS_MONITORING / FDCAN_MODE_RESTRICTED_OPERATION + hCAN1.Init.AutoRetransmission = DISABLE; // Auto Retransmission of message if error occured, can cause lockup + hCAN1.Init.TransmitPause = DISABLE; // Transmit Pause to allow for other device to send message + hCAN1.Init.ProtocolException = DISABLE; // ProtocolException handling + + hCAN1.Init.NominalPrescaler = 4; // Arbitration/data clock prescaler (1-512) + hCAN1.Init.NominalSyncJumpWidth = 1; // Arbitration/data sync jump width (1-128) + hCAN1.Init.NominalTimeSeg1 = 13; // Arbitration/data period 1 (2-256) + hCAN1.Init.NominalTimeSeg2 = 2; // Arbitration/data period 2 (2-128) + + /* Not used, no bitrate switching + hCAN1.Init.DataPrescaler = 4; // Arbitration/data clock prescaler (1-32) + hCAN1.Init.DataSyncJumpWidth = 1; // Arbitration/data sync jump width (1-16) + hCAN1.Init.DataTimeSeg1 = 13; // Arbitration/data period 1 (2-32) + hCAN1.Init.DataTimeSeg2 = 2; // Arbitration/data period 2 (2-16) */ + + hCAN1.Init.StdFiltersNbr = 2; // Number of standard ID frame filters, 2 for FIFO toggling + hCAN1.Init.ExtFiltersNbr = 2; // Number of extended ID frame filters, 2 for FIFO toggling + hCAN1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; // Queue mode: FDCAN_TX_FIFO_OPERATION / FDCAN_TX_QUEUE_OPERATION + // "FIFO OPERATION" means send in order of queueing, "QUEUE OPERATION" means send in order of ID priority + + status = HAL_FDCAN_Init(&hCAN1); // calls HAL_FDCAN_MspInit + if (status != HAL_OK) return status; + + FDCAN_FilterTypeDef sFilterConfig; // Configure RX message filter + sFilterConfig.IdType = FDCAN_EXTENDED_ID; // Filter to FIFO1 if higest ID bit is set + sFilterConfig.FilterIndex = 0; // Exteneded filter ID 0 + sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE + sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1; // Filter to FIFO1 + sFilterConfig.FilterID1 = 0x10000000; // Range: 0 - 0x1FFF FFFF, 0=don'tcare + sFilterConfig.FilterID2 = 0x10000000; // Range: 0 - 0x1FFF FFFF, 0=don'tcare + status = HAL_FDCAN_ConfigFilter(&hCAN1, &sFilterConfig); + if (status != HAL_OK) return status; + +//sFilterConfig.IdType = FDCAN_EXTENDED_ID; // Filter all remaining messages to FIFO0 + sFilterConfig.FilterIndex = 1; // Exteneded filter ID 1 +//sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE + sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; // Remaining messages go to FIFO0 + sFilterConfig.FilterID1 = 0; // Range: 0 - 0x1FFF FFFF, 0=don'tcare + sFilterConfig.FilterID2 = 0; // Range: 0 - 0x1FFF FFFF, 0=don'tcare + status = HAL_FDCAN_ConfigFilter(&hCAN1, &sFilterConfig); + if (status != HAL_OK) return status; + + sFilterConfig.IdType = FDCAN_STANDARD_ID; // Filter to FIFO1 if higest ID bit is set + sFilterConfig.FilterIndex = 0; // Standard filter ID 0 + sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE + sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1; // Filter to FIFO1 + sFilterConfig.FilterID1 = 0b10000000000; // Range: 0 - 0x7FF, 0=don't care + sFilterConfig.FilterID2 = 0b10000000000; // Range: 0 - 0x7FF, 0=don't care + status = HAL_FDCAN_ConfigFilter(&hCAN1, &sFilterConfig); + if (status != HAL_OK) return status; + +//sFilterConfig.IdType = FDCAN_STANDARD_ID; // Filter all remaining messages to FIFO0 + sFilterConfig.FilterIndex = 1; // Standard filter ID 1 +//sFilterConfig.FilterType = FDCAN_FILTER_MASK; // FDCAN_FILTER_MASK / FDCAN_FILTER_RANGE + sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; // Remaining messages go to FIFO0 + sFilterConfig.FilterID1 = 0; // Range: 0 - 0x7FF, 0=don't care + sFilterConfig.FilterID2 = 0; // Range: 0 - 0x7FF, 0=don't care + status = HAL_FDCAN_ConfigFilter(&hCAN1, &sFilterConfig); + if (status != HAL_OK) return status; + + status = HAL_FDCAN_Start(&hCAN1); + if (status != HAL_OK) return status; + + // Activate RX FIFO0 new message interrupt + status = HAL_FDCAN_ActivateNotification(&hCAN1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0); // Calls TIM16_IRQHandler (STM32G0xx) + if (status != HAL_OK) return status; + + // Activate RX FIFO0 message lost interrupt + status = HAL_FDCAN_ActivateNotification(&hCAN1, FDCAN_IT_RX_FIFO0_MESSAGE_LOST, 0); // Calls TIM16_IRQHandler (STM32G0xx) + if (status != HAL_OK) return status; + + // Activate RX FIFO1 new message interrupt + status = HAL_FDCAN_ActivateNotification(&hCAN1, FDCAN_IT_RX_FIFO1_NEW_MESSAGE, 0); // Calls TIM16_IRQHandler (STM32G0xx) + if (status != HAL_OK) return status; + + // Activate RX FIFO1 message lost interrupt + status = HAL_FDCAN_ActivateNotification(&hCAN1, FDCAN_IT_RX_FIFO1_MESSAGE_LOST, 0); // Calls TIM16_IRQHandler (STM32G0xx) + if (status != HAL_OK) return status; + + #if ENABLED(CAN_DEBUG) + + SERIAL_ECHOLNPGM(">>> Voltage Scaling: VOS", (PWR->CR1 & PWR_CR1_VOS_Msk) >> PWR_CR1_VOS_Pos); + SERIAL_ECHOLNPGM(">>> FDCAN Peripheral Clock: ", HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_FDCAN) / 1000000, "MHz"); + SERIAL_ECHOLNPGM(">>> FDCAN Timing. Prescaler: ", hCAN1.Init.NominalPrescaler, " SJW: ", hCAN1.Init.NominalSyncJumpWidth, " SEG1: ", hCAN1.Init.NominalTimeSeg1, " SEG2: ", hCAN1.Init.NominalTimeSeg2); + SERIAL_ECHOLNPGM(">>> FDCAN Bit sample point: ", 100 * (hCAN1.Init.NominalSyncJumpWidth + hCAN1.Init.NominalTimeSeg1)/ (hCAN1.Init.NominalSyncJumpWidth + hCAN1.Init.NominalTimeSeg1 + hCAN1.Init.NominalTimeSeg2), "%"); + uint32_t baudrate = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_FDCAN) / hCAN1.Init.NominalPrescaler / (hCAN1.Init.NominalSyncJumpWidth + hCAN1.Init.NominalTimeSeg1 + hCAN1.Init.NominalTimeSeg2); + SERIAL_ECHOLNPGM(">>> FDCAN BAUDRATE: ", baudrate); + + if (baudrate != CAN_BAUDRATE) { + SERIAL_ECHOLNPGM(">>> ", CAN_ERROR_MSG_INVALID_BAUDRATE, ": ", baudrate, " CAN_BAUDRATE=", CAN_BAUDRATE); + CAN_toolhead_error_code |= CAN_ERROR_TOOLHEAD_INVALID_BAUDRATE; + } + + #endif + + #ifdef CAN_LED_PIN + pinMode(CAN_LED_PIN, OUTPUT); + digitalWrite(CAN_LED_PIN, HIGH); + #endif + + return status; +} // CAN_toolhead_start + +// Send an IO status update to the host, and the E0 temperature if requested +void CAN_toolhead_send_update(bool TempUpdate) { // Called from temperature ISR! + + // Send a IO/temp report from the toolhead to the host + FDCAN_TxHeaderTypeDef CanTxHeader; + uint8_t can_tx_buffer[8]; // Transmit FDCAN message buffer + + // Initialize standard TxHeader values + CanTxHeader.FDFormat = FDCAN_CLASSIC_CAN; // FDCAN_CLASSIC_CAN / FDCAN_FD_CAN + CanTxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; // FDCAN_NO_TX_EVENTS / FDCAN_STORE_TX_EVENTS + CanTxHeader.MessageMarker = 0; // 0-0xFF for tracking messages in FIFO buffer + CanTxHeader.ErrorStateIndicator = FDCAN_ESI_PASSIVE; // FDCAN_ESI_PASSIVE / FDCAN_ESI_ACTIVE + CanTxHeader.TxFrameType = FDCAN_DATA_FRAME; // FDCAN_DATA_FRAME / FDCAN_REMOTE_FRAME + CanTxHeader.IdType = FDCAN_STANDARD_ID; // FDCAN_STANDARD_ID / FDCAN_EXTENDED_ID + CanTxHeader.BitRateSwitch = FDCAN_BRS_OFF; // FDCAN_BRS_OFF / FDCAN_BRS_ON + + if (TempUpdate) { // Add temperature update to FDCAN message, 4 bytes + float * fp = (float *)can_tx_buffer; // Point to FDCAN TX buffer + *fp = thermalManager.degHotend(0); // Copy temp to can_tx_buffer + CanTxHeader.DataLength = FDCAN_DLC_BYTES_4; // Hotend temp in payload only + } + else + CanTxHeader.DataLength = FDCAN_DLC_BYTES_0; // 0 data byte FDCAN message, virtual IO is encoded in the message ID + + // Message might be important, don't wait, make room for the message + if (!HAL_FDCAN_GetTxFifoFreeLevel(&hCAN1)) { // No TX slots available + HAL_FDCAN_AbortTxRequest(&hCAN1, FDCAN_TX_BUFFER0 | FDCAN_TX_BUFFER1 | FDCAN_TX_BUFFER2); // Flush all message buffers + CAN_toolhead_error_code |= CAN_ERROR_TOOLHEAD_TX_FIFO_OVERFLOW; + } + + CanTxHeader.Identifier = CAN_get_virtual_IO(); + + if (CAN_request_time_sync) { + + // For time sync request, wait for empty TX slot before requesting + if (HAL_FDCAN_GetTxFifoFreeLevel(&hCAN1) == CAN_FIFO_DEPTH) { + CanTxHeader.Identifier |= (1 << CAN_ID_REQUEST_TIME_SYNC_BIT_POS); // Issue time sync request + + CAN_request_time_sync = false; + NTP[0] = micros(); // Store time sync request message send time + } + } + + HAL_FDCAN_AddMessageToTxFifoQ(&hCAN1, &CanTxHeader, can_tx_buffer); + +} // CAN_toolhead_send_update + +// Send a string message to the host +void CAN_toolhead_send_string(const char * message) { + + FDCAN_TxHeaderTypeDef CanTxHeader; + uint8_t can_tx_buffer[8]; // Transmit CAN message buffer + + CAN_string_buffer.append(message); + + // Make sure there is a '\n' at the end of the string + if (CAN_string_buffer[CAN_string_buffer.length() - 1] != '\n') { + if (CAN_string_buffer.length() == CAN_MAX_STRING_MESSAGE_LENGTH) + CAN_string_buffer.trunc(CAN_MAX_STRING_MESSAGE_LENGTH - 1); + CAN_string_buffer.append('\n'); + } + + SERIAL_ECHOPGM(">>> STRING MSG TO HOST: "); + CAN_string_buffer.echo(); +} + +void CAN_send_next_string_part() { + + uint32_t len = CAN_string_buffer.length(); + + FDCAN_TxHeaderTypeDef CanTxHeader; + uint8_t can_tx_buffer[8]; // Transmit CAN message buffer + + // Initialize standard TX header values + CanTxHeader.FDFormat = FDCAN_CLASSIC_CAN; // FDCAN_CLASSIC_CAN / FDCAN_FD_CAN + CanTxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; // FDCAN_NO_TX_EVENTS / FDCAN_STORE_TX_EVENTS + CanTxHeader.MessageMarker = 0; // 0-0xFF for tracking messages in FIFO buffer + CanTxHeader.ErrorStateIndicator = FDCAN_ESI_PASSIVE; // FDCAN_ESI_PASSIVE / FDCAN_ESI_ACTIVE + CanTxHeader.TxFrameType = FDCAN_DATA_FRAME; // FDCAN_DATA_FRAME / FDCAN_REMOTE_FRAME + CanTxHeader.IdType = FDCAN_STANDARD_ID; // FDCAN_STANDARD_ID / FDCAN_EXTENDED_ID + CanTxHeader.BitRateSwitch = FDCAN_BRS_OFF; // FDCAN_BRS_OFF / FDCAN_BRS_ON + + uint32_t c = MIN(8, len); + CanTxHeader.DataLength = (c << CAN_DATALENGTH_OFFSET); // Max message length is 8 bytes (CAN), offset is 16 bits into the DataLength variable + + CanTxHeader.Identifier = CAN_get_virtual_IO() | (1 << CAN_ID_STRING_MESSAGE_BIT_POS); + + // Low priority message, wait until TX FIFO is completely empty before sending the message + if (HAL_FDCAN_GetTxFifoFreeLevel(&hCAN1) == CAN_FIFO_DEPTH) { + HAL_FDCAN_AddMessageToTxFifoQ(&hCAN1, &CanTxHeader, (uint8_t *)&CAN_string_buffer); // Send message + + if (c < 8) + CAN_string_buffer.clear(); + else + strncpy(CAN_string_buffer, (char *)(&CAN_string_buffer + c), len - c + 1); + } + +} // CAN_send_next_string_part + +void CAN_process_time_sync() { + // NTP style time sync + // NTP[0] = local time sync request time + // NTP[1] = host time sync receive time + // NTP[2] = host time sync reply time + // NTP[3] = local time stamp receive time + NTP[0] += CAN_NTP_time_offset; // Adjust local time stamps with CAN_NTP_time_offset + NTP[3] += CAN_NTP_time_offset; // Adjust local time stamps with CAN_NTP_time_offset + int CAN_local_time_adjustment = (NTP[1] - NTP[0] + NTP[2] - NTP[3]) >> 1; + CAN_NTP_time_offset += CAN_local_time_adjustment; + uint32_t CAN_Round_trip_delay = (NTP[3] - NTP[0] - NTP[2] + NTP[1]); + SERIAL_ECHOLNPGM(">>> t0: ", NTP[0], " us (Local time sync request time)"); + SERIAL_ECHOLNPGM(">>> t1: ", NTP[1], " us (remote time sync arrival time)"); + SERIAL_ECHOLNPGM(">>> t2: ", NTP[2], " us (remote time sync response time)"); + SERIAL_ECHOLNPGM(">>> t3: ", NTP[3], " us (local time sync reply receive time)"); + + if (CAN_NTP_clock_drift) + SERIAL_ECHOLNPGM(">>> Predicted time adjustment: ", CAN_NTP_clock_drift * float(NTP[0] - NTP[4]) / 1000000.0 , " us"); + + SERIAL_ECHOPGM(">>> Local time adjustment: ", CAN_local_time_adjustment, " us"); + if (NTP[4]) { + SERIAL_ECHOLNPGM(" after ", ftostr42_52(float(NTP[0] - NTP[4]) / 1000000.0), " seconds"); + CAN_NTP_clock_drift = CAN_local_time_adjustment / (float(NTP[0] - NTP[4]) / 1000000.0); // Calculate drift again + SERIAL_ECHOLNPGM(">>> Clock drift: ", CAN_NTP_clock_drift, " us/s"); + } + else + SERIAL_EOL(); + + SERIAL_ECHOLNPGM(">>> Time offset: ", CAN_NTP_time_offset, " us"); + SERIAL_ECHOLNPGM(">>> Round trip delay: ", CAN_Round_trip_delay, " us"); + SERIAL_ECHOLNPGM(">>> Host response time: ", (NTP[2] - NTP[1]), " us"); + + NTP[4] = NTP[0] + CAN_local_time_adjustment; // Store previous time sync request time + NTP[3] = 0; + +} + +void CAN_handle_errors() { + + uint32_t ms = millis(); + + if (hCAN1.ErrorCode) { + + if (ELAPSED(ms, CAN_next_error_message_time)) { // 12 seconds repeat + MString<40> tmp_string(F("Error: CAN Error Code = "), hCAN1.ErrorCode); + CAN_toolhead_send_string(tmp_string); + CAN_next_error_message_time += 12000; // Delay repeat of message + } + } + + if (CAN_toolhead_error_code) { + + if (ELAPSED(ms, CAN_next_led_flash_time)) { // Flash LED fast on error + TOGGLE(LED_PIN); + CAN_next_led_flash_time = ms + 100; + } + + if (CAN_toolhead_error_code != CAN_previous_error_code) { // Show message on new error code + + #if ENABLED(CAN_DEBUG) + + if (CAN_toolhead_error_code & CAN_ERROR_TOOLHEAD_RX_FIFO_OVERFLOW) { + CAN_toolhead_send_string(CAN_ERROR_MSG_RX_FIFO_OVERFLOW); + SERIAL_ECHOLNPGM(">>> ", CAN_ERROR_MSG_RX_FIFO_OVERFLOW); + } + + if (CAN_toolhead_error_code & CAN_ERROR_TOOLHEAD_TX_FIFO_OVERFLOW) { + CAN_toolhead_send_string(CAN_ERROR_MSG_TX_FIFO_OVERFLOW); + SERIAL_ECHOLNPGM(">>> ", CAN_ERROR_MSG_TX_FIFO_OVERFLOW); + } + + if (CAN_toolhead_error_code & CAN_ERROR_TOOLHEAD_INCOMPLETE_GCODE_RECEIVED) { + CAN_toolhead_send_string(CAN_ERROR_MSG_INCOMPLETE_GCODE); + SERIAL_ECHOLNPGM(">>> ", CAN_ERROR_MSG_INCOMPLETE_GCODE); + } + + if (CAN_toolhead_error_code & CAN_ERROR_TOOLHEAD_MARLIN_CMD_BUFFER_OVERFLOW) { + CAN_toolhead_send_string(CAN_ERROR_MSG_INCOMPLETE_GCODE); + SERIAL_ECHOLNPGM(">>> ", CAN_ERROR_MSG_INCOMPLETE_GCODE); + } + #else + SString<40> string_buffer(F("Error: TOOLHEAD error code="), CAN_toolhead_error_code); + CAN_toolhead_send_string(CAN_string_buffer); + CAN_string_buffer.echoln(); + #endif + CAN_previous_error_code = CAN_toolhead_error_code; + } + } +} // CAN_handle_errors + +void CAN_toolhead_idle() { // Called from MarlinCore.cpp + + // Send temperature update to host + if (ELAPSED(millis(), CAN_next_temp_report_time)) { + CAN_toolhead_send_update(true); + CAN_next_temp_report_time = millis() + CAN_TEMPERATURE_REPORT_INTERVAL; + } + + // Process any new CAN messages + if (CAN_queue_head != CAN_queue_tail) + process_can_queue(); + + // Send next part of a string message to the host + if (CAN_string_buffer.length()) { + if (ELAPSED(millis(), CAN_send_next_string_part_time)) { + CAN_send_next_string_part(); + CAN_send_next_string_part_time = millis() + 3; // Delay a bit, don't overload the half-duplex CAN bus + } + } + + // NTP style time sync + if (NTP[3]) // Indicates a host timestamp was received + CAN_process_time_sync(); + + CAN_handle_errors(); + +} // CAN_send_string + +#endif // CAN_TOOLHEAD diff --git a/Marlin/src/HAL/STM32/HardwareSerial.cpp b/Marlin/src/HAL/STM32/HardwareSerial.cpp index 2a389447b78f..275a8550799e 100644 --- a/Marlin/src/HAL/STM32/HardwareSerial.cpp +++ b/Marlin/src/HAL/STM32/HardwareSerial.cpp @@ -37,7 +37,17 @@ #include "HardwareSerial.h" #include "uart.h" -// USART/UART PIN MAPPING FOR STM32F0/F1/F2/F4/F7 +// Prevent selection of LPUART1 on STM32H7xx +#if defined(STM32H7xx) && (PIN_SERIAL1_TX == PA_9) + #undef PIN_SERIAL1_TX + #define PIN_SERIAL1_TX PA_9_ALT1 +#endif +#if defined(STM32H7xx) && (PIN_SERIAL1_RX == PA_10) + #undef PIN_SERIAL1_RX + #define PIN_SERIAL1_RX PA_10_ALT1 +#endif + +// USART/UART pin mapping for STM32F0/F1/F2/F4/F7/H7 #ifndef PIN_SERIAL1_TX #define PIN_SERIAL1_TX PA9 #endif @@ -75,46 +85,6 @@ #define PIN_SERIAL6_RX PC7 #endif -// TODO: Get from include file - -#if ANY(STM32F2xx, STM32F4xx, STM32F7xx) - - #define RCC_AHB1Periph_DMA1 ((uint32_t)0x00200000) - #define RCC_AHB1Periph_DMA2 ((uint32_t)0x00400000) - - void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState) { - // Check the parameters - assert_param(IS_RCC_AHB1_CLOCK_PERIPH(RCC_AHB1Periph)); - - assert_param(IS_FUNCTIONAL_STATE(NewState)); - if (NewState != DISABLE) - RCC->AHB1ENR |= RCC_AHB1Periph; - else - RCC->AHB1ENR &= ~RCC_AHB1Periph; - } - -#endif - -#if ANY(STM32F0xx, STM32F1xx) - - #define RCC_AHBPeriph_DMA1 ((uint32_t)0x00000001) - #define RCC_AHBPeriph_DMA2 ((uint32_t)0x00000002) - - void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState) { - /* Check the parameters */ - assert_param(IS_RCC_AHB_PERIPH(RCC_AHBPeriph)); - assert_param(IS_FUNCTIONAL_STATE(NewState)); - - if (NewState != DISABLE) - RCC->AHBENR |= RCC_AHBPeriph; - else - RCC->AHBENR &= ~RCC_AHBPeriph; - } - -#endif - -// END OF TODO------------------------------------------------------ - // SerialEvent functions are weak, so when the user doesn't define them, // the linker just sets their address to 0 (which is checked below). #ifdef USING_HW_SERIAL1 @@ -161,11 +131,12 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { setRx(PIN_SERIAL1_RX); setTx(PIN_SERIAL1_TX); _uart_index = 0; - #ifdef DMA2_Stream2 - RX_DMA = { USART1, RCC_AHB1Periph_DMA2, 4, DMA2_Stream2 }; + + #ifdef DMA2_Stream2 // F2 / F4 / F7 / H7 + RX_DMA = { USART1, 2, DMA2_Stream2 }; // USART, DMA controller no., DMA stream #endif - #ifdef DMA1_Channel5 - RX_DMA = { USART1, RCC_AHBPeriph_DMA1, DMA1, DMA1_Channel5 }; + #ifdef DMA1_Channel5 // F0 / F1 + RX_DMA = { USART1, 1, DMA1_Channel5 }; // USART, DMA controller no., DMA channel #endif } else if (peripheral == USART2) { @@ -173,10 +144,10 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { setTx(PIN_SERIAL2_TX); _uart_index = 1; #ifdef DMA1_Stream5 - RX_DMA = { USART2, RCC_AHB1Periph_DMA1, 4, DMA1_Stream5 }; + RX_DMA = { USART2, 1, DMA1_Stream5 }; #endif #ifdef DMA1_Channel6 - RX_DMA = { USART2, RCC_AHBPeriph_DMA1, DMA1, DMA1_Channel6 }; + RX_DMA = { USART2, 1, DMA1_Channel6 }; #endif } else if (peripheral == USART3) { @@ -184,17 +155,17 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { setTx(PIN_SERIAL3_TX); _uart_index = 2; #ifdef DMA1_Stream1 - RX_DMA = { USART3, RCC_AHB1Periph_DMA1, 4, DMA1_Stream1 }; + RX_DMA = { USART3, 1, DMA1_Stream1 }; #endif #ifdef DMA1_Channel3 // F0 has no support for UART3, requires system remapping - RX_DMA = { USART3, RCC_AHBPeriph_DMA1, DMA1, DMA1_Channel3 }; + RX_DMA = { USART3, 1, DMA1_Channel3 }; #endif } #ifdef USART4 // Only F2 / F4 / F7 else if (peripheral == USART4) { #ifdef DMA1_Stream2 - RX_DMA = { USART4, RCC_AHB1Periph_DMA1, 4, DMA1_Stream2 }; + RX_DMA = { USART4, 1, DMA1_Stream2 }; #endif setRx(PIN_SERIAL4_RX); setTx(PIN_SERIAL4_TX); @@ -205,10 +176,10 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { #ifdef UART4 else if (peripheral == UART4) { #ifdef DMA1_Stream2 - RX_DMA = { UART4, RCC_AHB1Periph_DMA1, 4, DMA1_Stream2 }; + RX_DMA = { UART4, 1, DMA1_Stream2 }; #endif #ifdef DMA2_Channel3 // STM32F0xx has only 3 UARTs - RX_DMA = { UART4, RCC_AHBPeriph_DMA2, DMA2, DMA2_Channel3 }; + RX_DMA = { UART4, 2, DMA2_Channel3 }; #endif setRx(PIN_SERIAL4_RX); setTx(PIN_SERIAL4_TX); @@ -216,10 +187,10 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { } #endif - #ifdef UART5 // Only F2 / F4 / F7 + #ifdef UART5 // Only F2 / F4 / F7 / H7 else if (peripheral == UART5) { #ifdef DMA1_Stream0 - RX_DMA = { UART5, RCC_AHB1Periph_DMA1, 4, DMA1_Stream0 }; + RX_DMA = { UART5, 1, DMA1_Stream0 }; #endif setRx(PIN_SERIAL5_RX); setTx(PIN_SERIAL5_TX); @@ -227,10 +198,10 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { } #endif - #ifdef USART6 // Only F2 / F4 / F7 + #ifdef USART6 // Only F2 / F4 / F7 / H7 else if (peripheral == USART6) { #ifdef DMA2_Stream1 - RX_DMA = { USART6, RCC_AHB1Periph_DMA2, 4, DMA2_Stream1 }; + RX_DMA = { USART6, 2, DMA2_Stream1 }; #endif setRx(PIN_SERIAL6_RX); setTx(PIN_SERIAL6_TX); @@ -271,14 +242,34 @@ void HAL_HardwareSerial::init(PinName _rx, PinName _tx) { * @param obj : pointer to serial_t structure * @retval last character received */ -int HAL_HardwareSerial::_tx_complete_irq(serial_t *obj) { - // If interrupts are enabled, there must be more data in the output buffer. Send the next byte - obj->tx_tail = (obj->tx_tail + 1) % TX_BUFFER_SIZE; - if (obj->tx_head == obj->tx_tail) return -1; +#if DISABLED(STM32H7xx) - return 0; -} + int HAL_HardwareSerial::_tx_complete_irq(serial_t *obj) { + // If interrupts are enabled, there must be more data in the output buffer. Send the next byte + obj->tx_tail = (obj->tx_tail + 1) % TX_BUFFER_SIZE; + if (obj->tx_head == obj->tx_tail) + return -1; + + return 0; + } + +#else // STM32H7xx, has different uart_attach_tx_callback + + int HAL_HardwareSerial::_tx_complete_irq(serial_t *obj) { + // If interrupts are enabled, there must be more data in the output buffer. Send the next byte + obj->tx_tail = (obj->tx_tail + obj->tx_size) % TX_BUFFER_SIZE; + + if (obj->tx_head != obj->tx_tail) { + size_t remaining_data = (TX_BUFFER_SIZE + obj->tx_head - obj->tx_tail) % TX_BUFFER_SIZE; + obj->tx_size = min(remaining_data, (size_t)(TX_BUFFER_SIZE - obj->tx_tail)); + uart_attach_tx_callback(obj, _tx_complete_irq, obj->tx_size); + return -1; + } + return 0; + } + +#endif // Public Methods ////////////////////////////////////////////////////////////// @@ -340,33 +331,37 @@ void HAL_HardwareSerial::update_rx_head() { } #endif - #if ANY(STM32F2xx, STM32F4xx, STM32F7xx) + #if ANY(STM32F2xx, STM32F4xx, STM32F7xx, STM32H7xx) _serial.rx_head = RX_BUFFER_SIZE - RX_DMA.dma_streamRX->NDTR; #endif #if ANY(STM32F0xx, STM32F1xx) _serial.rx_head = RX_BUFFER_SIZE - RX_DMA.dma_channelRX->CNDTR; #endif - } int HAL_HardwareSerial::available() { update_rx_head(); + return ((unsigned int)(RX_BUFFER_SIZE + _serial.rx_head - _serial.rx_tail)) % RX_BUFFER_SIZE; } int HAL_HardwareSerial::peek() { update_rx_head(); - if (_serial.rx_head == _serial.rx_tail) return -1; + if (_serial.rx_head == _serial.rx_tail) + return -1; + return _serial.rx_buff[_serial.rx_tail]; } int HAL_HardwareSerial::read() { update_rx_head(); - if (_serial.rx_head == _serial.rx_tail) return -1; // No chars if the head isn't ahead of the tail + if (_serial.rx_head == _serial.rx_tail) + return -1; // No chars if the head isn't ahead of the tail unsigned char c = _serial.rx_buff[_serial.rx_tail]; _serial.rx_tail = (rx_buffer_index_t)(_serial.rx_tail + 1) % RX_BUFFER_SIZE; + return c; } @@ -380,8 +375,18 @@ size_t HAL_HardwareSerial::write(uint8_t c) { // Interrupt based wri _serial.tx_buff[_serial.tx_head] = c; _serial.tx_head = i; - if (!serial_tx_active(&_serial)) - uart_attach_tx_callback(&_serial, _tx_complete_irq); // Write next byte, launch interrupt + #ifdef STM32H7xx // Support STM32H7xx with different uart_attach_tx_callback + if ((!serial_tx_active(&_serial)) && (_serial.tx_head != _serial.tx_tail)) { + size_t remaining_data = (TX_BUFFER_SIZE + _serial.tx_head -_serial.tx_tail) % TX_BUFFER_SIZE; + _serial.tx_size = min(remaining_data, (size_t)(TX_BUFFER_SIZE - _serial.tx_tail)); + uart_attach_tx_callback(&_serial, _tx_complete_irq, _serial.tx_size); + + return -1; + } + #else + if (!serial_tx_active(&_serial)) + uart_attach_tx_callback(&_serial, _tx_complete_irq); // Write next byte, launch interrupt + #endif return 1; } @@ -390,57 +395,144 @@ void HAL_HardwareSerial::flush() { while ((_serial.tx_head != _serial.tx_tail)) { /* nada */ } // nop, the interrupt handler will free up space for us } -#if ANY(STM32F2xx, STM32F4xx, STM32F7xx) +#if ANY(STM32F2xx, STM32F4xx, STM32F7xx, STM32H7xx) void HAL_HardwareSerial::Serial_DMA_Read_Enable() { - RCC_AHB1PeriphClockCmd(RX_DMA.dma_rcc, ENABLE); // Enable DMA clock - #ifdef STM32F7xx - RX_DMA.dma_streamRX->PAR = (uint32_t)(&RX_DMA.uart->RDR); // RX peripheral receive address (usart) F7 + if (RX_DMA.DMA_ID == 1) + __HAL_RCC_DMA1_CLK_ENABLE(); // Enable DMA1 clock + else + __HAL_RCC_DMA2_CLK_ENABLE(); // Enable DMA2 clock + + // Reset DMA, wait if needed to complete the running process + RX_DMA.dma_streamRX->CR = 0; // DMA stream clear/disable + while (RX_DMA.dma_streamRX->CR & DMA_SxCR_EN) { /* just wait for DMA to complete */ } + + // UART clear/disable + RX_DMA.uart->CR1 = 0; + + // Configure DMA + #if ANY(STM32F7xx, STM32H7xx) // F7 and H7 use RDR (Receive Data Register) + RX_DMA.dma_streamRX->PAR = (uint32_t)(&RX_DMA.uart->RDR); // DMA stream Peripheral Address Register = USART Data Register #else - RX_DMA.dma_streamRX->PAR = (uint32_t)(&RX_DMA.uart->DR); // RX peripheral address (usart) F2 / F4 + RX_DMA.dma_streamRX->PAR = (uint32_t)(&RX_DMA.uart->DR); // DMA stream Peripheral Address Register = USART Data Register #endif - RX_DMA.dma_streamRX->M0AR = (uint32_t)_serial.rx_buff; // RX destination address (memory) - RX_DMA.dma_streamRX->NDTR = RX_BUFFER_SIZE; // RX buffer size - RX_DMA.dma_streamRX->CR = (RX_DMA.dma_channel << 25); // RX channel selection, set to 0 all the other CR bits + RX_DMA.dma_streamRX->M0AR = (uint32_t)_serial.rx_buff; // DMA stream Memory 0 Adress Register = RX buffer address + RX_DMA.dma_streamRX->NDTR = RX_BUFFER_SIZE; // DMA stream Number of Data Transfer Register + + #if DISABLED(STM32H7xx) // Select channel via CR register + + RX_DMA.dma_streamRX->CR = 4 << DMA_SxCR_CHSEL_Pos; // DMA stream Channel Selection, always use channel 4 + + #else // STM32H7xx, select channel with DMAMUX1, channel DMA1 is channel DMAMUX, channel DMA2 is channel DMAMUX + 8 + + if (RX_DMA.uart == USART1) + DMAMUX1_Channel10->CCR |= DMA_REQUEST_USART1_RX; // DMA2, Stream 2 - RX_DMA.dma_streamRX->CR |= (3 << 16); // RX priority level: Very High + if (RX_DMA.uart == USART2) + DMAMUX1_Channel5->CCR |= DMA_REQUEST_USART2_RX; // DMA1, Stream 5 - //RX_DMA.dma_streamRX->CR &= ~(3 << 13); // RX memory data size: 8 bit - //RX_DMA.dma_streamRX->CR &= ~(3 << 11); // RX peripheral data size: 8 bit - RX_DMA.dma_streamRX->CR |= (1 << 10); // RX memory increment mode - //RX_DMA.dma_streamRX->CR &= ~(1 << 9); // RX peripheral no increment mode - RX_DMA.dma_streamRX->CR |= (1 << 8); // RX circular mode enabled - //RX_DMA.dma_streamRX->CR &= ~(1 << 6); // RX data transfer direction: Peripheral-to-memory - RX_DMA.uart->CR3 |= (1 << 6); // Enable DMA receiver (DMAR) - RX_DMA.dma_streamRX->CR |= (1 << 0); // RX enable DMA + if (RX_DMA.uart == USART3) + DMAMUX1_Channel1->CCR |= DMA_REQUEST_USART3_RX; // DMA1, Stream 1 + #ifdef UART4 + if (RX_DMA.uart == UART4) + DMAMUX1_Channel2->CCR |= DMA_REQUEST_UART4_RX; // DMA1, Stream 2 + #endif + + #ifdef USART4 + if (RX_DMA.uart == USART4) + DMAMUX1_Channel2->CCR |= DMA_REQUEST_USART4_RX; // DMA1, Stream 2 + #endif + + #ifdef UART5 + if (RX_DMA.uart == UART5) + DMAMUX1_Channel0->CCR |= DMA_REQUEST_UART5_RX; // DMA1, Stream 0 + #endif + + #ifdef USART6 + if (RX_DMA.uart == USART6) + DMAMUX1_Channel9->CCR |= DMA_REQUEST_USART6_RX; // DMA2, Stream 1 + #endif + + #endif // !STM32H7xx + + // Configure DMA + //RX_DMA.dma_streamRX->CR |= DMA_MBURST_SINGLE; // DMA stream Memory Burst transfer: single transfer = 0b00 + //RX_DMA.dma_streamRX->CR |= DMA_PBURST_SINGLE; // DMA stream Peripheral Burst transfer: single transfer = 0b00 + + #if ENABLED(STM32H7xx) + RX_DMA.dma_streamRX->CR |= DMA_SxCR_TRBUFF; // DMA stream Transfer handle bufferable (required for UART/USART) + #endif + + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_CT; // DMA stream Current Target (only in double buffer mode) + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_DBM; // DMA stream Double Buffer Mode + //RX_DMA.dma_streamRX->CR |= DMA_PRIORITY_LOW; // DMA stream Priority Level Low = 0b00 + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_PINCOS; // DMA stream Peripheral Increment Offset Size + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_MSIZE; // DMA stream Memory data Size: 8 bit = 0b00 + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_PSIZE; // DMA stream Peripheral data Size: 8 bit = 0b00 + RX_DMA.dma_streamRX->CR |= DMA_SxCR_MINC; // DMA stream Memory Increment enable + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_PINC; // DMA stream Peripheral increment + RX_DMA.dma_streamRX->CR |= DMA_SxCR_CIRC; // DMA stream Circular mode enable + //RX_DMA.dma_streamRX->CR |= DMA_PERIPH_TO_MEMORY; // DMA stream transfer Direction: Peripheral-to-memory = b00 + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_PFCTRL; // DMA stream Peripheral Flow Controller: DMA = 0 + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_TCIE; // DMA stream Transfer Complete Interrupt + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_HTIE; // DMA stream Half Transfer Interrupt + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_TEIE; // DMA stream Transfer Error Interrupt + //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_DMEIE; // DMA stream Direct Mode Error Interrupt + RX_DMA.dma_streamRX->CR |= DMA_SxCR_EN; // DMA stream Enable + + // Configure UART/USART + RX_DMA.uart->CR3 |= USART_CR3_DMAR; // UART DMA Receiver + RX_DMA.uart->CR1 |= USART_CR1_TE; // UART Transmitter Enable + RX_DMA.uart->CR1 |= USART_CR1_RE; // UART Receiver Enable + RX_DMA.uart->CR1 |= USART_CR1_UE; // UART Enable } -#endif // STM32F2xx || STM32F4xx || STM32F7xx +#endif // STM32F2xx || STM32F4xx || STM32F7xx || STM32H7xx #if ANY(STM32F0xx, STM32F1xx) void HAL_HardwareSerial::Serial_DMA_Read_Enable() { - RCC_AHBPeriphClockCmd(RX_DMA.dma_rcc, ENABLE); // enable DMA clock - RX_DMA.dma_channelRX->CPAR = (uint32_t)(&RX_DMA.uart->DR); // RX peripheral address (usart) - RX_DMA.dma_channelRX->CMAR = (uint32_t)_serial.rx_buff; // RX destination address (memory) - RX_DMA.dma_channelRX->CNDTR = RX_BUFFER_SIZE; // RX buffer size + if (RX_DMA.DMA_ID == 1) + __HAL_RCC_DMA1_CLK_ENABLE(); // enable DMA1 clock + else + __HAL_RCC_DMA2_CLK_ENABLE(); // enable DMA2 clock - RX_DMA.dma_channelRX->CCR = 0; // RX channel selection, set to 0 all the other CR bits + RX_DMA.dma_channelRX->CCR &= ~USART_CR1_UE; // DMA stream clear/disable + while (RX_DMA.dma_channelRX->CCR & DMA_CCR_EN) { /* just wait for DMA to complete */ } + + // Clear/disable UART and DMA + RX_DMA.uart->CR1 = 0; // UART clear CR1, disable DMA - RX_DMA.dma_channelRX->CCR |= (3<<12); // RX priority level: Very High + // Configure DMA - //RX_DMA.dma_channelRX->CCR &= ~(1<<10); // RX memory data size: 8 bit - //RX_DMA.dma_channelRX->CCR &= ~(1<<8); // RX peripheral data size: 8 bit - RX_DMA.dma_channelRX->CCR |= (1<<7); // RX memory increment mode - //RX_DMA.dma_channelRX->CCR &= ~(1<<6); // RX peripheral no increment mode - RX_DMA.dma_channelRX->CCR |= (1<<5); // RX circular mode enabled - //RX_DMA.dma_channelRX->CCR &= ~(1<<4); // RX data transfer direction: Peripheral-to-memory + #ifdef STM32F0xx + RX_DMA.dma_channelRX->CPAR = (uint32_t)(&RX_DMA.uart->RDR); // DMA channel Peripheral Address Register = USART Data Register + #else + RX_DMA.dma_channelRX->CPAR = (uint32_t)(&RX_DMA.uart->DR); // DMA channel Peripheral Address Register = USART Data Register + #endif - RX_DMA.uart->CR3 |= (1<<6); // enable DMA receiver (DMAR) - RX_DMA.dma_channelRX->CCR |= (1<<0); // RX enable DMA + RX_DMA.dma_channelRX->CMAR = (uint32_t)_serial.rx_buff; // DMA channel Memory Address Register + RX_DMA.dma_channelRX->CNDTR = RX_BUFFER_SIZE; // DMA channel Number of Data Transfer Register + //RX_DMA.dma_channelRX->CCR |= (0b00 << DMA_CCR_PL_Pos); // DMA channel Priority Level: Low = 0b00 + //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_MSIZE; // DMA channel Data Size: 8 bit = 0 + //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_PSIZE; // DMA channel Peripheral data size: 8 bit = 0 + RX_DMA.dma_channelRX->CCR |= DMA_CCR_MINC; // DMA channel Memory Increment enable + //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_PINC; // DMA channel Peripheral Increment disable + RX_DMA.dma_channelRX->CCR |= DMA_CCR_CIRC; // DMA channel Circular mode enable + //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_DIR; // DMA channel Data Transfer direction: 0=Read peripheral, 1=Read memory + //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_TEIE; // DMA channel Transfer Error Interrupt + //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_HTIE; // DMA channel Half Transfer Interrupt + //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_TCIE; // DMA channel Transfer Complete Interrupt + RX_DMA.dma_channelRX->CCR |= DMA_CCR_EN; // DMA channel enable + + // Configure UART/USART + RX_DMA.uart->CR3 |= USART_CR3_DMAR; // UART DMA Receiver enabled + RX_DMA.uart->CR1 |= USART_CR1_TE; // UART Transmitter Enable + RX_DMA.uart->CR1 |= USART_CR1_RE; // UART Receiver Enable + RX_DMA.uart->CR1 |= USART_CR1_UE; // UART Enable } #endif // STM32F0xx || STM32F1xx diff --git a/Marlin/src/HAL/STM32/HardwareSerial.h b/Marlin/src/HAL/STM32/HardwareSerial.h index cc564322b4d5..cf976372782a 100644 --- a/Marlin/src/HAL/STM32/HardwareSerial.h +++ b/Marlin/src/HAL/STM32/HardwareSerial.h @@ -38,12 +38,10 @@ typedef struct { USART_TypeDef * uart; - uint32_t dma_rcc; + uint32_t DMA_ID; // DMA1=1; DM2=2; BDMA=3 #if ANY(STM32F0xx, STM32F1xx) // F0 / F1 - DMA_TypeDef * dma_controller; DMA_Channel_TypeDef * dma_channelRX; - #else // F2 / F4 / F7 - uint32_t dma_channel; + #else // F2 / F4 / F7 / H7 DMA_Stream_TypeDef * dma_streamRX; #endif } DMA_CFG; diff --git a/Marlin/src/HAL/STM32/Servo.cpp b/Marlin/src/HAL/STM32/Servo.cpp index 5ef3e62f1c6a..67d9773d7c1d 100644 --- a/Marlin/src/HAL/STM32/Servo.cpp +++ b/Marlin/src/HAL/STM32/Servo.cpp @@ -29,8 +29,8 @@ #include "Servo.h" -#if ENABLED(CAN_MASTER) - #include "../shared/CAN.h" +#if ENABLED(CAN_HOST) + #include "../shared/CAN_host.h" #endif static uint_fast8_t servoCount = 0; @@ -76,15 +76,15 @@ int8_t libServo::attach(const int pin, const int min, const int max) { void libServo::move(const int value) { - #if ENABLED(CAN_MASTER) // Forward direct Servo command to head + #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_Send_Gcode_2params('M', 401, 0, 0, 0, 0); // Deploy Angle: Send "M401" instead, enables interrupt etc. + 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_Send_Gcode_2params('M', 402, 0, 0, 0, 0); // Stow Angle: Send "M402" instead, enables interrupt etc. + CAN_host_send_gcode_2params('M', 402, 0, 0, 0, 0); // Stow Angle: Send "M402" instead, enables interrupt etc. else - CAN_Send_Gcode_2params('M', 280, 'S', value, 'P', 0); // M280 S[value] P0 + CAN_host_send_gcode_2params('M', 280, 'S', value, 'P', 0); // M280 S[value] P0 #endif if (attach(0) >= 0) { diff --git a/Marlin/src/HAL/STM32/eeprom_flash.cpp b/Marlin/src/HAL/STM32/eeprom_flash.cpp index 37963ad50154..d649775d45cb 100644 --- a/Marlin/src/HAL/STM32/eeprom_flash.cpp +++ b/Marlin/src/HAL/STM32/eeprom_flash.cpp @@ -74,12 +74,24 @@ #define EEPROM_SLOTS ((FLASH_UNIT_SIZE) / (MARLIN_EEPROM_SIZE)) #define SLOT_ADDRESS(slot) (FLASH_ADDRESS_START + (slot * (MARLIN_EEPROM_SIZE))) +#ifdef STM32H7xx + #define FLASHWORD_SIZE 32U // STM32H7xx a FLASHWORD is 32 bytes (256 bits) + #define UNLOCK_FLASH() if (!flash_unlocked) { \ + HAL_FLASH_Unlock(); \ + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | \ + FLASH_FLAG_PGSERR); \ + flash_unlocked = true; \ + } +#else + #define FLASHWORD_SIZE 4U // STM32F4xx a FLASHWORD is 4 bytes sizeof(uint32_t) #define UNLOCK_FLASH() if (!flash_unlocked) { \ HAL_FLASH_Unlock(); \ __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | \ FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR); \ flash_unlocked = true; \ } +#endif + #define LOCK_FLASH() if (flash_unlocked) { HAL_FLASH_Lock(); flash_unlocked = false; } #define EMPTY_UINT32 ((uint32_t)-1) @@ -88,7 +100,7 @@ static uint8_t ram_eeprom[MARLIN_EEPROM_SIZE] __attribute__((aligned(4))) = {0}; static int current_slot = -1; - static_assert(0 == MARLIN_EEPROM_SIZE % 4, "MARLIN_EEPROM_SIZE must be a multiple of 4"); // Ensure copying as uint32_t is safe + static_assert(0 == MARLIN_EEPROM_SIZE % FLASHWORD_SIZE, "MARLIN_EEPROM_SIZE must be a multiple of the FLASHWORD size"); // Ensure copying as uint32_t is safe static_assert(0 == FLASH_UNIT_SIZE % MARLIN_EEPROM_SIZE, "MARLIN_EEPROM_SIZE must divide evenly into your FLASH_UNIT_SIZE"); static_assert(FLASH_UNIT_SIZE >= MARLIN_EEPROM_SIZE, "FLASH_UNIT_SIZE must be greater than or equal to your MARLIN_EEPROM_SIZE"); static_assert(IS_FLASH_SECTOR(FLASH_SECTOR), "FLASH_SECTOR is invalid"); @@ -147,9 +159,12 @@ bool PersistentStore::access_start() { bool PersistentStore::access_finish() { if (eeprom_data_written) { - #ifdef STM32F4xx + + #if STM32H7xx // MCU may come up with flash error bits which prevent some flash operations. // Clear flags prior to flash operations to prevent errors. + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGSERR); + #else __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR); #endif @@ -194,13 +209,17 @@ bool PersistentStore::access_finish() { data = 0; bool success = true; - while (address < address_end) { - memcpy(&data, ram_eeprom + offset, sizeof(data)); - status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data); + + #if STM32H7xx + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, address, uint32_t(ram_eeprom + offset)); + #else + memcpy(&data, ram_eeprom + offset, sizeof(data)); + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data); + #endif if (status == HAL_OK) { - address += sizeof(uint32_t); - offset += sizeof(uint32_t); + address += FLASHWORD_SIZE; + offset += FLASHWORD_SIZE; } else { DEBUG_ECHOLNPGM("HAL_FLASH_Program=", status); diff --git a/Marlin/src/HAL/STM32/inc/SanityCheck.h b/Marlin/src/HAL/STM32/inc/SanityCheck.h index e35b4e59cf81..93bc2aef37b5 100644 --- a/Marlin/src/HAL/STM32/inc/SanityCheck.h +++ b/Marlin/src/HAL/STM32/inc/SanityCheck.h @@ -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) diff --git a/Marlin/src/HAL/shared/CAN.h b/Marlin/src/HAL/shared/CAN.h deleted file mode 100644 index c15bbb854364..000000000000 --- a/Marlin/src/HAL/shared/CAN.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * 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" - -//#define CAN_DEBUG // Define to show gcodes send to HEAD - -#define SOUND_OK 880 -#define SOUND_ERROR 40 - -extern uint32_t CAN_io_state; // CAN virtual IO variable - -#define CAN_IO_MASK 0b11111 // Masks for the 5 virtual IO bits -#define CAN_PROBE_MASK 1 // Virtual IO bit -#define CAN_FILAMENT_MASK 2 // Virtual IO bit -#define CAN_X_ENDSTOP_MASK 4 // Virtual IO bit -#define CAN_Y_ENDSTOP_MASK 8 // Virtual IO bit -#define CAN_Z_ENDSTOP_MAS 16 // Virtual IO bit -#define CAN_STRING_MESSAGE_MASK 32 // Signals the head sends a string message -#define CAN_REQUEST_SETUP_MASK 64 // Signals the head requests setup information -#define CAN_TMC_OT_MASK 128 // Signals the head signals a TMC Over Temp error -#define CAN_CAN_REQUEST_TIME_SYNC_MASK 256 // Signals the head requested a time sync -#define CAN_ERROR_MASK 512 // Signals the head encountered an error - -HAL_StatusTypeDef CAN1_Start(); // FUNCTION PROTOTYPES -HAL_StatusTypeDef CAN1_Stop(); -HAL_StatusTypeDef CAN_Send_Gcode(); -HAL_StatusTypeDef CAN_Send_Gcode_2params(uint32_t Gcode_type, uint32_t Gcode_no, uint32_t parameter1, float value1, uint32_t parameter2, float value2); -void CAN_Send_Setup(); // Send host configuration to head -void CAN_Idle(); // Idle CAN task diff --git a/Marlin/src/HAL/shared/CAN_host.h b/Marlin/src/HAL/shared/CAN_host.h new file mode 100644 index 000000000000..0b2f9c9e5226 --- /dev/null +++ b/Marlin/src/HAL/shared/CAN_host.h @@ -0,0 +1,108 @@ +/** + * 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" + +#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_MASK (1 << 0) // Virtual IO bit for Z-probe pin +#define CAN_ID_PROBE_BIT_POS 0 +#define CAN_ID_FILAMENT_MASK (1 << 1) // Virtual IO bit for filament pin +#define CAN_ID_FILAMENT_BIT_POS 1 +#define CAN_ID_X_ENDSTOP_MASK (1 << 2) // Virtual IO bit for X-min pin +#define CAN_ID_X_ENDSTOP_BIT_POS 2 +#define CAN_ID_Y_ENDSTOP_MASK (1 << 3) // Virtual IO bit for Y-min pin +#define CAN_ID_Y_ENDSTOP_BIT_POS 3 +#define CAN_ID_Z_ENDSTOP_MASK (1 << 4) // Virtual IO bit for Z-min pin +#define CAN_ID_Z_ENDSTOP_BIT_POS 4 +#define CAN_ID_STRING_MESSAGE_MASK (1 << 5) // Signals the toolhead sent a string message +#define CAN_ID_STRING_MESSAGE_BIT_POS 5 +#define CAN_ID_REQUEST_SETUP_MASK (1 << 6) // Signals the toolhead requests setup information +#define CAN_ID_REQUEST_SETUP_BIT_POS 6 +#define CAN_ID_TMC_OT_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_MASK (1 << 8) // Signals the toolhead requested a time sync +#define CAN_ID_REQUEST_TIME_SYNC_BIT_POS 8 +#define CAN_ID_ERROR_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 "Toolhead CAN RX FIFO overflow" +#define CAN_ERROR_MSG_TX_FIFO_OVERFLOW "Toolhead CAN TX FIFO overflow" +#define CAN_ERROR_MSG_INCOMPLETE_GCODE "Toolhead incomplete Gcode message received" +#define CAN_ERROR_MSG_MARLIN_CMM_BUF_OVERFLOW "Toolhead Marlin CMD buffer overflow" +#define CAN_ERROR_MSG_INVALID_BAUDRATE "Toolhead Incorrect CAN baudrate" + + + +void CAN_host_idle(); // CAN idle task +void CAN_host_send_setup(); // 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(); \ No newline at end of file diff --git a/Marlin/src/HAL/shared/FDCAN.h b/Marlin/src/HAL/shared/FDCAN.h deleted file mode 100644 index 6a8712deea01..000000000000 --- a/Marlin/src/HAL/shared/FDCAN.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -// IMPORTANT NOTES -// =============== -// In \Users\\.platformio\packages\framework-arduinoststm32@4.20600.231001\libraries\SrcWrapper\src\HardwareTimer.cpp -// Add function "__weak" in front of "void TIM16_IRQHandler(void)" -// NOTE: Every CAN message is accepted in FIFO0 if: 1. No Standard/Extended filters are set. 2. No global filters is set. - -#define CAN_IO_MASK 0b11111 // Virtual IO Mask, 5 bits are used for IO signalling -#define CAN_PROBE_BIT 0 // Virtual IO bit -#define CAN_FILAMENT_BIT 1 // Virtual IO bit -#define CAN_X_ENDSTOP_BIT 2 // Virtual IO bit -#define CAN_Y_ENDSTOP_BIT 3 // Virtual IO bit -#define CAN_Z_ENDSTOP_BIT 4 // Virtual IO bit -#define CAN_STRING_MESSAGE_BIT 5 // Signals the head sends a string message -#define CAN_REQUEST_SETUP_BIT 6 // Signals the head requests setup information -#define CAN_TMC_OT_BIT 7 // Signals the head encountered a TMC Over Temp error -#define CAN_REQUEST_TIME_SYNC_BIT 8 // Signals a request for time sync -#define CAN_ERROR_BIT 9 // Signals the head encountered an error - -#define GCODE_TYPE_D 0 -#define GCODE_TYPE_G 1 -#define GCODE_TYPE_M 2 -#define GCODE_TYPE_T 3 - -#define IDENTIFIER_PARAMTER1_OFFSET 0 -#define IDENTIFIER_PARAMTER2_OFFSET 5 -#define IDENTIFIER_GCODE_NUMBER_OFFSET 10 -#define IDENTIFIER_GCODE_TYPE_OFFSET 23 -#define IDENTIFIER_PARAMETER_COUNT_OFFSET 25 - -#define DATALENGTH_OFFSET 16 - -#define IDENTIFIER_PARAMETER_COUNT_MASK 0b111 -#define IDENTIFIER_PARAMETER_MASK 0b11111 -#define IDENTIFIER_GCODE_TYPE_MASK 0b11 // GCODE TYPE -#define IDENTIFIER_GCODE_NUMBER_MASK 0b1111111111111 // GCODE NUMBER ONLY -#define IDENTIFIER_GCODE_MASK 0b111111111111111 // GCODE TYPE AND NUMBER - -#define STDID_FIFO_BIT 0b10000000000 - -// ERROR CODES -#define CAN_ERROR_FIFO_OVERFLOW 1 -#define CAN_ERROR_INCOMPLETE_GCODE_RECEIVED 2 -#define CAN_ERROR_MARLIN_CMD_BUFFER_OVERFLOW 4 - -HAL_StatusTypeDef CAN_Send_String(const char * message); // Send CAN string to host -HAL_StatusTypeDef FDCAN2_Start(void); // Start the CAN bus \ No newline at end of file diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index 307f45f34011..d7a320fcc436 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -34,12 +34,8 @@ #include "HAL/shared/esp_wifi.h" #include "HAL/shared/cpu_exception/exception_hook.h" -#if ENABLED(CAN_MASTER) - #include "HAL/shared/CAN.h" -#endif - -#if ENABLED(CAN_TOOLHEAD) - #include "HAL/shared/FDCAN.h" +#if ANY(CAN_HOST, CAN_TOOLHEAD) + #include "HAL/shared/CAN_host.h" #endif #if ENABLED(HAS_ADXL345_ACCELEROMETER) @@ -894,15 +890,9 @@ void idle(const bool no_stepper_sleep/*=false*/) { // Manage Fixed-time Motion Control TERN_(FT_MOTION, ftMotion.loop()); -#if ENABLED(CAN_MASTER) - void CAN_idle(); // Function Prototype - CAN_idle(); // Call CAN idle task -#endif + TERN_(CAN_HOST, CAN_host_idle()); -#if ENABLED(CAN_TOOLHEAD) - void FDCAN_idle(); // Function prototype - FDCAN_idle(); // Call FDCAN idle task -#endif // CAN_TOOLHEAD + TERN_(CAN_TOOLHEAD, CAN_toolhead_idle()); IDLE_DONE: TERN_(MARLIN_DEV_MODE, idle_depth--); @@ -1212,7 +1202,7 @@ void setup() { while (!MYSERIAL3.connected() && PENDING(millis(), serial_connect_timeout)) { /*nada*/ } #endif #endif - SERIAL_ECHOLNPGM("start\n"); + SERIAL_ECHOLNPGM("start"); // Set up these pins early to prevent suicide #if HAS_KILL @@ -1258,19 +1248,20 @@ void setup() { SETUP_RUN(hal.init()); - #if ENABLED(CAN_MASTER) + #if ENABLED(CAN_HOST) SERIAL_ECHOLN( - F(">>> CAN1 Start: "), - CAN1_Start() == HAL_OK ? F("OK") : F("FAILED!") + F(">>> CAN Start: "), + CAN_host_start() == HAL_OK ? F("OK") : F("FAILED!") ); #endif #if ENABLED(CAN_TOOLHEAD) - SERIAL_ECHOLN( - F(">>> FDCAN2 Start: "), - FDCAN2_Start() == HAL_OK ? F("OK") : F("FAILED!") - ); + SERIAL_ECHOLN(millis(), + F(">>> CAN Start: "), + CAN_toolhead_start() == HAL_OK ? F("OK") : F("FAILED!") + ); #endif + #if ENABLED(HAS_ADXL345_ACCELEROMETER) adxl345.begin(); #endif diff --git a/Marlin/src/feature/runout.h b/Marlin/src/feature/runout.h index aab51cc9ac8e..5d3637d367af 100644 --- a/Marlin/src/feature/runout.h +++ b/Marlin/src/feature/runout.h @@ -33,8 +33,8 @@ #include "pause.h" // for did_pause_print #include "../MarlinCore.h" // for printingIsActive() -#if ENABLED(CAN_MASTER) - #include "../HAL/shared/CAN.h" +#if ENABLED(CAN_HOST) + #include "../HAL/shared/CAN_host.h" #endif #include "../inc/MarlinConfig.h" @@ -55,9 +55,9 @@ #define HAS_FILAMENT_SWITCH 1 #endif -#if ENABLED(CAN_MASTER) - #define FILAMENT_IS_OUT() (bool(CAN_io_state & CAN_FILAMENT_MASK) == FIL_RUNOUT_STATE) - #define RUNOUT_STATE(N) bool(CAN_io_state & CAN_FILAMENT_MASK) // CAN Virtual Filament Runout pin +#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 diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp index 3744a384886b..984b63ea8f11 100644 --- a/Marlin/src/gcode/gcode.cpp +++ b/Marlin/src/gcode/gcode.cpp @@ -69,8 +69,8 @@ GcodeSuite gcode; #include "../feature/fancheck.h" #endif -#if ENABLED(CAN_MASTER) - #include "../HAL/shared/CAN.h" +#if ENABLED(CAN_HOST) + #include "../HAL/shared/CAN_host.h" #include "../libs/buzzer.h" #endif @@ -328,10 +328,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) { KEEPALIVE_STATE(IN_HANDLER); - #if ENABLED(CAN_MASTER) - if (CAN_Send_Gcode() != HAL_OK) { // Send command to head + #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, '"'); - #ifndef CAN_DEBUG + #if ENABLED(CAN_DEBUG) BUZZ(1, SOUND_ERROR); #endif } diff --git a/Marlin/src/gcode/host/M115.cpp b/Marlin/src/gcode/host/M115.cpp index a4a2ac405f57..cd2f6c4da805 100644 --- a/Marlin/src/gcode/host/M115.cpp +++ b/Marlin/src/gcode/host/M115.cpp @@ -35,10 +35,18 @@ #include "../../feature/caselight.h" #endif +#if ENABLED(CAN_TOOLHEAD) + #include "../../HAL/shared/CAN_host.h" +#endif + #if !defined(MACHINE_UUID) && ENABLED(HAS_STM32_UID) #include "../../libs/hex_print.h" #endif +#if ENABLED(CAN_HOST) + #include "../../HAL/shared/CAN_host.h" +#endif + //#define MINIMAL_CAP_LINES // Don't even mention the disabled capabilities #if ENABLED(EXTENDED_CAPABILITIES_REPORT) @@ -76,6 +84,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 diff --git a/Marlin/src/gcode/temp/M306.cpp b/Marlin/src/gcode/temp/M306.cpp index 511106201fe0..9889d90d2c0a 100644 --- a/Marlin/src/gcode/temp/M306.cpp +++ b/Marlin/src/gcode/temp/M306.cpp @@ -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_host.h" +#endif /** * M306: MPC settings and autotune @@ -58,10 +63,10 @@ void GcodeSuite::M306() { #if ENABLED(MPC_AUTOTUNE) if (parser.seen_test('T')) { - #if ENABLED(CAN_MASTER) // MPC Autotune info + #if ENABLED(CAN_HOST) // Execute MPC autotune on toolhead SERIAL_ECHOLNPGM( - ">>> Forwarding M306 to head board\n" + ">>> 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..." @@ -81,7 +86,7 @@ void GcodeSuite::M306() { ui.reset_status(); #if ENABLED(CAN_TOOLHEAD) - M306_report(true); // Report M306 settings to CAN host + M306_report(true); // Report MPC autotune results to CAN host #endif #endif @@ -110,7 +115,7 @@ void GcodeSuite::M306_report(const bool forReplay/*=true*/) { report_heading(forReplay, F("Model predictive control")); - #if ENABLED(CAN_MASTER) // MPC Autotune info + #if ENABLED(CAN_HOST) // MPC Autotune info if (forReplay) SERIAL_ECHOLNPGM(">>> Host M306 MPC settings:"); #endif @@ -139,13 +144,12 @@ void GcodeSuite::M306_report(const bool forReplay/*=true*/) { #if ENABLED(MPC_INCLUDE_FAN) " F", p_float_t(mpc.fanCoefficient(), 4), #endif - " H", p_float_t(mpc.filament_heat_capacity_permm, 4), - '\n'); + " H", p_float_t(mpc.filament_heat_capacity_permm, 4)); + + CAN_toolhead_send_string(buffer); + } +#endif // CAN_TOOLHEAD - HAL_StatusTypeDef CAN_Send_String(const char * message); // Function Prototype - CAN_Send_String(buffer); - } - #endif // CAN_TOOLHEAD } #endif // MPCTEMP diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index 9ec17b28089e..b3576280c806 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -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)." diff --git a/Marlin/src/module/endstops.cpp b/Marlin/src/module/endstops.cpp index 97cbde21242a..db20ffcfa988 100644 --- a/Marlin/src/module/endstops.cpp +++ b/Marlin/src/module/endstops.cpp @@ -65,6 +65,10 @@ #include "probe.h" #endif +#if ENABLED(CAN_TOOLHEAD) + #include "../HAL/shared/CAN_host.h" +#endif + #define DEBUG_OUT ALL(USE_SENSORLESS, DEBUG_LEVELING_FEATURE) #include "../core/debug_out.h" @@ -84,7 +88,7 @@ Endstops::endstop_mask_t Endstops::live_state = 0; #else #define READ_ENDSTOP(P) READ(P) #endif -#elif ENABLED(CAN_MASTER) // Read virtual CAN IO Probe status if needed +#elif ENABLED(CAN_HOST) // Read virtual CAN IO probe status if needed #if HAS_BED_PROBE #define READ_ENDSTOP(P) ((P == Z_MIN_PIN) ? PROBE_READ() : READ(P)) #else @@ -678,10 +682,9 @@ void Endstops::update() { if (probe_switch_activated()) UPDATE_LIVE_STATE(Z, TERN(USE_Z_MIN_PROBE, MIN_PROBE, MIN)); - #if ENABLED(CAN_TOOLHEAD) - HAL_StatusTypeDef CAN_Send_Message(bool TempUpdate); // Function Prototype - CAN_Send_Message(false); // Send Virtual IO update without temperature report - #endif // CAN_TOOLHEAD + #if ENABLED(CAN_TOOLHEAD) // Forward endstop interrupt to host + CAN_toolhead_send_update(false); // Send Virtual IO update without temperature report + #endif #endif diff --git a/Marlin/src/module/probe.h b/Marlin/src/module/probe.h index 41d59bb91afb..496ea205a463 100644 --- a/Marlin/src/module/probe.h +++ b/Marlin/src/module/probe.h @@ -29,8 +29,8 @@ #include "motion.h" -#if ENABLED(CAN_MASTER) - #include "../HAL/shared/CAN.h" +#if ENABLED(CAN_HOST) + #include "../HAL/shared/CAN_host.h" #endif #if ENABLED(BLTOUCH) @@ -51,8 +51,8 @@ #if ENABLED(BD_SENSOR) #define PROBE_READ() bdp_state -#elif ENABLED(CAN_MASTER) - #define PROBE_READ() bool(CAN_io_state & CAN_PROBE_MASK) +#elif ENABLED(CAN_HOST) + #define PROBE_READ() bool(CAN_host_get_iostate() & CAN_ID_PROBE_MASK) #elif USE_Z_MIN_PROBE #define PROBE_READ() READ(Z_MIN_PROBE_PIN) #else diff --git a/Marlin/src/module/settings.cpp b/Marlin/src/module/settings.cpp index 51677fdec6b8..27e60be02db0 100644 --- a/Marlin/src/module/settings.cpp +++ b/Marlin/src/module/settings.cpp @@ -735,6 +735,10 @@ void MarlinSettings::postprocess() { TERN_(EXTENSIBLE_UI, ExtUI::onPostprocessSettings()); + #if ENABLED(CAN_HOST) + CAN_host_send_setup(); // Update toolhead settings + #endif + // Refresh mm_per_step with the reciprocal of axis_steps_per_mm // and init stepper.count[], planner.position[] with current_position planner.refresh_positioning(); diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp index b2b80893cedd..f509d0f23989 100644 --- a/Marlin/src/module/temperature.cpp +++ b/Marlin/src/module/temperature.cpp @@ -50,8 +50,8 @@ #include "motion.h" #endif -#if ENABLED(CAN_MASTER) - #include "../HAL/shared/CAN.h" +#if ENABLED(CAN_HOST) + #include "../HAL/shared/CAN_host.h" #endif #if ENABLED(DWIN_CREALITY_LCD) @@ -2738,7 +2738,7 @@ void Temperature::updateTemperaturesFromRawValues() { temp_bed.setraw(read_max_tc_bed()); #endif - #if HAS_HOTEND && DISABLED(CAN_MASTER) // For CAN Master we'll get temperature from CAN bus + #if HAS_HOTEND && DISABLED(CAN_HOST) // For CAN Host we'll get temperature from CAN bus HOTEND_LOOP() temp_hotend[e].celsius = analog_to_celsius_hotend(temp_hotend[e].getraw(), e); #endif @@ -2753,7 +2753,7 @@ void Temperature::updateTemperaturesFromRawValues() { TERN_(FILAMENT_WIDTH_SENSOR, filwidth.update_measured_mm()); TERN_(HAS_POWER_MONITOR, power_monitor.capture_values()); - #if HAS_HOTEND && DISABLED(CAN_MASTER) // Only for Head, no temp sampling on Master + #if HAS_HOTEND && DISABLED(CAN_HOST) // Only for toolhead, no temp sampling on Host #define _TEMPDIR(N) TEMP_SENSOR_IS_ANY_MAX_TC(N) ? 0 : TEMPDIR(N), static constexpr int8_t temp_dir[HOTENDS] = { REPEAT(HOTENDS, _TEMPDIR) }; @@ -2781,7 +2781,7 @@ void Temperature::updateTemperaturesFromRawValues() { } } - #endif // HAS_HOTEND && !CAN_MASTER + #endif // HAS_HOTEND && !CAN_HOST #if ENABLED(THERMAL_PROTECTION_BED) if (TP_CMP(BED, temp_bed.getraw(), temp_sensor_range_bed.raw_max)) @@ -3381,10 +3381,10 @@ void Temperature::disable_all_heaters() { #if HAS_HOTEND - #if ENABLED(CAN_MASTER) // Shut down the hotend in the head too - CAN_Send_Gcode_2params('M', 104, 'S', 0, 0, 0); // M104 S0 .... Switch off hotend heating - CAN_Send_Gcode_2params('M', 107, 0, 0, 0, 0); // M107 ....... Switch off part cooling fan - CAN_Send_Gcode_2params('M', 150, 'R', 255, 0, 0); // M150 R255 .. Set NeoPixel to red + #if ENABLED(CAN_HOST) // Shut down the hotend in the head too + CAN_host_send_gcode_2params('M', 104, 'S', 0, 0, 0); // M104 S0 .... Switch off hotend heating + CAN_host_send_gcode_2params('M', 107, 0, 0, 0, 0); // M107 ....... Switch off part cooling fan + CAN_host_send_gcode_2params('M', 150, 'R', 255, 0, 0); // M150 R255 .. Set NeoPixel to red #endif HOTEND_LOOP() { @@ -4432,7 +4432,6 @@ void Temperature::isr() { endstops.poll(); #if ENABLED(CAN_TOOLHEAD) - HAL_StatusTypeDef CAN_Send_Message(bool TempUpdate); // Function Prototype static uint32_t loopCounter = 0; if ((loopCounter++ % 512) == 0) // Update E0 Temp every 512ms CAN_Send_Message(true); // Send temp report with IO report diff --git a/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h b/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h index 422374ac80b0..955b5d46e243 100644 --- a/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h +++ b/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h @@ -44,7 +44,7 @@ #define I2C_EEPROM // Need use jumpers set i2c for EEPROM #define MARLIN_EEPROM_SIZE 0x1000 // 4K -#if ENALBED(CAN_MASTER) // Onboard I2C EEPROM is disconnected by enabling CAN bus jumpers +#if ENALBED(CAN_HOST) // Onboard I2C EEPROM is disconnected by enabling CAN bus jumpers // NOTE: 2 Patch wires are required to reconnect the I2C EEPROM //#define I2C_SCL_PIN PA8 // BLTouch servo control pin //#define I2C_SDA_PIN PC9 // SPI3_CS TFT Card chip select pin From 2536a093a07453af0c4c6a0bc6c61373ecc3d70b Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Sat, 11 Jan 2025 03:53:40 +0800 Subject: [PATCH 15/21] Fix a few error defines --- Marlin/src/HAL/STM32/CAN_host.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Marlin/src/HAL/STM32/CAN_host.cpp b/Marlin/src/HAL/STM32/CAN_host.cpp index abef545d721e..e86a792fe027 100644 --- a/Marlin/src/HAL/STM32/CAN_host.cpp +++ b/Marlin/src/HAL/STM32/CAN_host.cpp @@ -132,7 +132,7 @@ void CAN_host_send_timestamp() { // Request receive timestamp + request response HAL_CAN_AddTxMessage(&hCAN1, &TxHeader, CAN_tx_buffer, &TxMailbox); // Queue CAN message } else - CAN_host_error_code |= CAN_ERROR_TX_MSG_DROPPED; + CAN_host_error_code |= CAN_ERROR_HOST_TX_MSG_DROPPED; } @@ -209,7 +209,7 @@ HAL_StatusTypeDef CAN_host_send_gcode_2params(uint32_t Gcode_type, uint32_t Gcod if (HAL_CAN_GetTxMailboxesFreeLevel(&hCAN1)) status = HAL_CAN_AddTxMessage(&hCAN1, &TxHeader, CAN_tx_buffer, &TxMailbox); // Queue CAN message else - CAN_host_error_code |= CAN_ERROR_TX_MSG_DROPPED; + CAN_host_error_code |= CAN_ERROR_HOST_TX_MSG_DROPPED; return status; } @@ -527,7 +527,7 @@ HAL_StatusTypeDef CAN_host_send_gcode() { // Forward a Marlin Gcode via CAN (use } if (parameter_counter == 8) { // Max is 7 parameters - CAN_host_error_code |= CAN_ERROR_INVALID_GCODE; + CAN_host_error_code |= CAN_ERROR_HOST_INVALID_GCODE; parameter_counter--; SERIAL_ECHOLNPGM("\nError: TOO MANY PARAMETERS (> 7): ", parser.command_ptr); BUZZ(1, SOUND_ERROR); @@ -566,7 +566,7 @@ HAL_StatusTypeDef CAN_host_send_gcode() { // Forward a Marlin Gcode via CAN (use if (HAL_CAN_GetTxMailboxesFreeLevel(&hCAN1)) status = HAL_CAN_AddTxMessage(&hCAN1, &TxHeader, CAN_tx_buffer, &TxMailbox); // Queue CAN message else - CAN_host_error_code |= CAN_ERROR_TX_MSG_DROPPED; + CAN_host_error_code |= CAN_ERROR_HOST_TX_MSG_DROPPED; if (status != HAL_OK) return status; From 53be094e9c14d78d27ac2deec771804dd49bcb6d Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Sat, 11 Jan 2025 12:35:07 +0800 Subject: [PATCH 16/21] Update to safer SString class --- Marlin/src/HAL/STM32/FDCAN_toolhead.cpp | 100 +++++++----------------- 1 file changed, 28 insertions(+), 72 deletions(-) diff --git a/Marlin/src/HAL/STM32/FDCAN_toolhead.cpp b/Marlin/src/HAL/STM32/FDCAN_toolhead.cpp index ede60ed4fa54..5a00d8e6e14d 100644 --- a/Marlin/src/HAL/STM32/FDCAN_toolhead.cpp +++ b/Marlin/src/HAL/STM32/FDCAN_toolhead.cpp @@ -84,7 +84,7 @@ struct CAN_MSG_BUFFER { // Struct to hold a CAN message uint32_t receive_time; // Used for time sync }; -SString CAN_string_buffer; // String buffer to hold outgoing string messages +SString CAN_string_buffer; // String buffer to hold outgoing string messages char gcode_type[4] = { 'D', 'G', 'M', 'T' }; // The 4 Gcode types bool CAN_toolhead_not_configured = true; // Track if the host sent the required toolhead configuration uint32_t CAN_toolhead_error_code = 0; // Signals an CAN error occured, report to host @@ -134,69 +134,12 @@ uint32_t CAN_get_virtual_IO() { ); } -// Convert a float to a string, formatted as used in Gcode commands, store the result at codeP -void floatToString(float value, char *codeP) { - - if (value < 0.0) { - *codeP++ = '-'; // Add minus sign to float - value = -value; // Make value positive for further processing - } - - uint32_t i1 = int(value); // Get integer part of float - itoa(i1, codeP, 10); - codeP += strlen(codeP); // Adjust pointer - value = (value - i1); // Get fractional part of float - - uint32_t j = 0; - for (int i = 0; i < 5; i++) { // Calculate 6 digits, 1 extra digit for rounding - value *= 10.0; - i1 = int(value); - j = (10 * j) + i1; - value = value - i1; - } - - if (value >= 0.5) - j++; // Round up - - if (j) { - *codeP++ = '.'; - if (j < 10000) { - *codeP++ = '0'; - if (j < 1000) { - *codeP++ = '0'; - if (j < 100) { - *codeP++ = '0'; - if (j < 10) - *codeP++ = '0'; - } - } - } - - while (!(j % 10)) // Remove trailing zeros, "120" --> "12" - j = j / 10; - - itoa(j, codeP, 10); - } -} - -// Add received parameters to gcode string at codeP (used in ISR!) -void CAN_add_gcode_parameters(uint32_t parameter, float value, char *codeP) { - - *codeP++ = char(parameter + 64); // Add Gcode parameter letter, e.g., 'X' - - if (!isnan(value)) // No value for parameter if value is "Not A Number" - floatToString(value, codeP); - else - *codeP = '\0'; -} - // Process a received message (offline in the idle handler) void process_can_queue() { - static char CAN_gcode_buffer[MAX_CMD_SIZE]; + static SString CAN_gcode_buffer; static uint32_t parameter_counter = 0; uint32_t identifier = CAN_QUEUE[CAN_queue_tail].identifier; - static char *codeP = CAN_gcode_buffer; // Put Gcode pointer to start of CAN_gcode_buffer; bool enqueue = true; // Receiving new Gcode @@ -232,7 +175,7 @@ void process_can_queue() { NTP[2] = *uint32p; NTP[3] = CAN_QUEUE[CAN_queue_tail].receive_time; // Record time sync response message receive time - enqueue = false; // Timestamps were stored, nothing else to do + enqueue = false; // Timestamps were stored, the idle process will handle the rest } // New Gcode started, so previous Gcode must be complete (all parameters received) @@ -241,36 +184,49 @@ void process_can_queue() { parameter_counter = (identifier >> CAN_ID_PARAMETER_COUNT_BIT_POS) & CAN_ID_PARAMETER_COUNT_MASK; // Get Gcode parameter count - *codeP++ = gcode_type[(identifier >> CAN_ID_GCODE_TYPE_BIT_POS) & CAN_ID_GCODE_TYPE_MASK]; // Add Gcode letter e.g., "G" - itoa((identifier >> CAN_ID_GCODE_NUMBER_BIT_POS) & CAN_ID_GCODE_NUMBER_MASK, codeP, 10); // Add Gcode number e.g., G"92" + CAN_gcode_buffer = gcode_type[(identifier >> CAN_ID_GCODE_TYPE_BIT_POS) & CAN_ID_GCODE_TYPE_MASK]; // Add Gcode letter e.g., "G" + CAN_gcode_buffer.append((identifier >> CAN_ID_GCODE_NUMBER_BIT_POS) & CAN_ID_GCODE_NUMBER_MASK); // Add Gcode number e.g., G"92" } + uint32_t backupLength = 0; // Backup string length in case we cannot enqueue the Gcode and have to try again // Add parameters (if present) to the received Gcode if (parameter_counter && ((identifier >> CAN_ID_PARAMETER1_BIT_POS) & CAN_ID_PARAMETER_LETTER_MASK)) { // Get 1st parameter, make sure it's not empty. - codeP += strlen(codeP); // Adjust the pointer to the already present data in the buffer - CAN_add_gcode_parameters(identifier & CAN_ID_PARAMETER_LETTER_MASK, CAN_QUEUE[CAN_queue_tail].data[0], codeP); + backupLength = CAN_gcode_buffer.length(); // Point to place where parameters are added + CAN_gcode_buffer.append(char((identifier & CAN_ID_PARAMETER_LETTER_MASK) + 64)); // Add Gcode parameter letter, e.g., 'X' + float value = CAN_QUEUE[CAN_queue_tail].data[0]; + if (!isnan(value)) { // No value for parameter if value is "Not A Number" + if (value == int(value)) + CAN_gcode_buffer.append(int(CAN_QUEUE[CAN_queue_tail].data[0])); // Integer value + else + CAN_gcode_buffer.append(p_float_t(CAN_QUEUE[CAN_queue_tail].data[0], 5)); + } parameter_counter--; // Add 2nd parameter if 2 values were provided if (parameter_counter && ((identifier >> CAN_ID_PARAMETER2_BIT_POS) & CAN_ID_PARAMETER_LETTER_MASK)) { // Get 2nd parameter, make sure it's not empty. - codeP += strlen(codeP); // Adjust the pointer to the already present data in the buffer - CAN_add_gcode_parameters((identifier >> CAN_ID_PARAMETER2_BIT_POS) & CAN_ID_PARAMETER_LETTER_MASK, CAN_QUEUE[CAN_queue_tail].data[1], codeP); + CAN_gcode_buffer.append(char(((identifier >> CAN_ID_PARAMETER2_BIT_POS) & CAN_ID_PARAMETER_LETTER_MASK) + 64)); // Add Gcode parameter letter, e.g., 'X' + if (!isnan(CAN_QUEUE[CAN_queue_tail].data[1])) { // No value for parameter if value is "Not A Number" + if (value == int(value)) + CAN_gcode_buffer.append(int(CAN_QUEUE[CAN_queue_tail].data[1])); // Integer value + else + CAN_gcode_buffer.append(p_float_t(CAN_QUEUE[CAN_queue_tail].data[1], 5)); + } parameter_counter--; } } if (!parameter_counter) { // Gcode is complete, including all parameters, process the Gcode - - codeP = CAN_gcode_buffer; // Move pointer to start of string - if (enqueue) { // queue.enqueue_one returns TRUE if the command was queued, FALSE if the Marlin cmd buffer was full - if (queue.enqueue_one(CAN_gcode_buffer)) { // Increase tail only when commands was handled - CAN_queue_tail = (CAN_queue_tail + 1) % CAN_QUEUE_DEPTH; // Advance tail only is command was enqueued + if (queue.enqueue_one(CAN_gcode_buffer)) { // Increase tail only when commands was enqueued + CAN_queue_tail = (CAN_queue_tail + 1) % CAN_QUEUE_DEPTH; #ifdef CAN_DEBUG - SERIAL_ECHOLNPGM(";", millis(), " ", CAN_gcode_buffer); + SERIAL_ECHOPGM(";", millis(), " "); CAN_gcode_buffer.echoln(); #endif } + else + if (!(identifier & CAN_EXTENDED_ID_MARKER_MASK)) // Standard ID message, so parameters were added to the Gcode + CAN_gcode_buffer.trunc(backupLength); // Cut off the part of the Gcode that was added, so we can process the CAN message again } else CAN_queue_tail = (CAN_queue_tail + 1) % CAN_QUEUE_DEPTH; // Always advance tail From f5e0b5eb14a8d92a82d1dbefb2083fdb558d5887 Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Sat, 11 Jan 2025 14:55:33 +0800 Subject: [PATCH 17/21] Removed STM32H7 SERIAL_DMA and Flash Wear leveling and accelerometer Split PR into several smaller PRs as requested, which makes testing easier. --- Marlin/Configuration_adv.h | 6 - Marlin/src/HAL/STM32/HardwareSerial.cpp | 292 ++++++------------ Marlin/src/HAL/STM32/HardwareSerial.h | 6 +- Marlin/src/HAL/STM32/eeprom_flash.cpp | 33 +- .../src/feature/accelerometer/acc_adxl345.cpp | 73 ----- .../src/feature/accelerometer/acc_adxl345.h | 98 ------ ini/features.ini | 1 - 7 files changed, 111 insertions(+), 398 deletions(-) delete mode 100644 Marlin/src/feature/accelerometer/acc_adxl345.cpp delete mode 100644 Marlin/src/feature/accelerometer/acc_adxl345.h diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index e5c6563064cc..564f44df606d 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -3496,12 +3496,6 @@ #endif // HAS_TRINAMIC_CONFIG -// @section spibus - -// This feature is EXPERIMENTAL -// Warning: Enabling the sensor will probably conflict with the onboard SD Card -//#define HAS_ADXL345_ACCELEROMETER; // uncomment to enable the 3-axis accelerometer - // @section i2cbus // diff --git a/Marlin/src/HAL/STM32/HardwareSerial.cpp b/Marlin/src/HAL/STM32/HardwareSerial.cpp index 275a8550799e..2a389447b78f 100644 --- a/Marlin/src/HAL/STM32/HardwareSerial.cpp +++ b/Marlin/src/HAL/STM32/HardwareSerial.cpp @@ -37,17 +37,7 @@ #include "HardwareSerial.h" #include "uart.h" -// Prevent selection of LPUART1 on STM32H7xx -#if defined(STM32H7xx) && (PIN_SERIAL1_TX == PA_9) - #undef PIN_SERIAL1_TX - #define PIN_SERIAL1_TX PA_9_ALT1 -#endif -#if defined(STM32H7xx) && (PIN_SERIAL1_RX == PA_10) - #undef PIN_SERIAL1_RX - #define PIN_SERIAL1_RX PA_10_ALT1 -#endif - -// USART/UART pin mapping for STM32F0/F1/F2/F4/F7/H7 +// USART/UART PIN MAPPING FOR STM32F0/F1/F2/F4/F7 #ifndef PIN_SERIAL1_TX #define PIN_SERIAL1_TX PA9 #endif @@ -85,6 +75,46 @@ #define PIN_SERIAL6_RX PC7 #endif +// TODO: Get from include file + +#if ANY(STM32F2xx, STM32F4xx, STM32F7xx) + + #define RCC_AHB1Periph_DMA1 ((uint32_t)0x00200000) + #define RCC_AHB1Periph_DMA2 ((uint32_t)0x00400000) + + void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState) { + // Check the parameters + assert_param(IS_RCC_AHB1_CLOCK_PERIPH(RCC_AHB1Periph)); + + assert_param(IS_FUNCTIONAL_STATE(NewState)); + if (NewState != DISABLE) + RCC->AHB1ENR |= RCC_AHB1Periph; + else + RCC->AHB1ENR &= ~RCC_AHB1Periph; + } + +#endif + +#if ANY(STM32F0xx, STM32F1xx) + + #define RCC_AHBPeriph_DMA1 ((uint32_t)0x00000001) + #define RCC_AHBPeriph_DMA2 ((uint32_t)0x00000002) + + void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState) { + /* Check the parameters */ + assert_param(IS_RCC_AHB_PERIPH(RCC_AHBPeriph)); + assert_param(IS_FUNCTIONAL_STATE(NewState)); + + if (NewState != DISABLE) + RCC->AHBENR |= RCC_AHBPeriph; + else + RCC->AHBENR &= ~RCC_AHBPeriph; + } + +#endif + +// END OF TODO------------------------------------------------------ + // SerialEvent functions are weak, so when the user doesn't define them, // the linker just sets their address to 0 (which is checked below). #ifdef USING_HW_SERIAL1 @@ -131,12 +161,11 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { setRx(PIN_SERIAL1_RX); setTx(PIN_SERIAL1_TX); _uart_index = 0; - - #ifdef DMA2_Stream2 // F2 / F4 / F7 / H7 - RX_DMA = { USART1, 2, DMA2_Stream2 }; // USART, DMA controller no., DMA stream + #ifdef DMA2_Stream2 + RX_DMA = { USART1, RCC_AHB1Periph_DMA2, 4, DMA2_Stream2 }; #endif - #ifdef DMA1_Channel5 // F0 / F1 - RX_DMA = { USART1, 1, DMA1_Channel5 }; // USART, DMA controller no., DMA channel + #ifdef DMA1_Channel5 + RX_DMA = { USART1, RCC_AHBPeriph_DMA1, DMA1, DMA1_Channel5 }; #endif } else if (peripheral == USART2) { @@ -144,10 +173,10 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { setTx(PIN_SERIAL2_TX); _uart_index = 1; #ifdef DMA1_Stream5 - RX_DMA = { USART2, 1, DMA1_Stream5 }; + RX_DMA = { USART2, RCC_AHB1Periph_DMA1, 4, DMA1_Stream5 }; #endif #ifdef DMA1_Channel6 - RX_DMA = { USART2, 1, DMA1_Channel6 }; + RX_DMA = { USART2, RCC_AHBPeriph_DMA1, DMA1, DMA1_Channel6 }; #endif } else if (peripheral == USART3) { @@ -155,17 +184,17 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { setTx(PIN_SERIAL3_TX); _uart_index = 2; #ifdef DMA1_Stream1 - RX_DMA = { USART3, 1, DMA1_Stream1 }; + RX_DMA = { USART3, RCC_AHB1Periph_DMA1, 4, DMA1_Stream1 }; #endif #ifdef DMA1_Channel3 // F0 has no support for UART3, requires system remapping - RX_DMA = { USART3, 1, DMA1_Channel3 }; + RX_DMA = { USART3, RCC_AHBPeriph_DMA1, DMA1, DMA1_Channel3 }; #endif } #ifdef USART4 // Only F2 / F4 / F7 else if (peripheral == USART4) { #ifdef DMA1_Stream2 - RX_DMA = { USART4, 1, DMA1_Stream2 }; + RX_DMA = { USART4, RCC_AHB1Periph_DMA1, 4, DMA1_Stream2 }; #endif setRx(PIN_SERIAL4_RX); setTx(PIN_SERIAL4_TX); @@ -176,10 +205,10 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { #ifdef UART4 else if (peripheral == UART4) { #ifdef DMA1_Stream2 - RX_DMA = { UART4, 1, DMA1_Stream2 }; + RX_DMA = { UART4, RCC_AHB1Periph_DMA1, 4, DMA1_Stream2 }; #endif #ifdef DMA2_Channel3 // STM32F0xx has only 3 UARTs - RX_DMA = { UART4, 2, DMA2_Channel3 }; + RX_DMA = { UART4, RCC_AHBPeriph_DMA2, DMA2, DMA2_Channel3 }; #endif setRx(PIN_SERIAL4_RX); setTx(PIN_SERIAL4_TX); @@ -187,10 +216,10 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { } #endif - #ifdef UART5 // Only F2 / F4 / F7 / H7 + #ifdef UART5 // Only F2 / F4 / F7 else if (peripheral == UART5) { #ifdef DMA1_Stream0 - RX_DMA = { UART5, 1, DMA1_Stream0 }; + RX_DMA = { UART5, RCC_AHB1Periph_DMA1, 4, DMA1_Stream0 }; #endif setRx(PIN_SERIAL5_RX); setTx(PIN_SERIAL5_TX); @@ -198,10 +227,10 @@ HAL_HardwareSerial::HAL_HardwareSerial(void *peripheral) { } #endif - #ifdef USART6 // Only F2 / F4 / F7 / H7 + #ifdef USART6 // Only F2 / F4 / F7 else if (peripheral == USART6) { #ifdef DMA2_Stream1 - RX_DMA = { USART6, 2, DMA2_Stream1 }; + RX_DMA = { USART6, RCC_AHB1Periph_DMA2, 4, DMA2_Stream1 }; #endif setRx(PIN_SERIAL6_RX); setTx(PIN_SERIAL6_TX); @@ -242,34 +271,14 @@ void HAL_HardwareSerial::init(PinName _rx, PinName _tx) { * @param obj : pointer to serial_t structure * @retval last character received */ +int HAL_HardwareSerial::_tx_complete_irq(serial_t *obj) { + // If interrupts are enabled, there must be more data in the output buffer. Send the next byte + obj->tx_tail = (obj->tx_tail + 1) % TX_BUFFER_SIZE; -#if DISABLED(STM32H7xx) - - int HAL_HardwareSerial::_tx_complete_irq(serial_t *obj) { - // If interrupts are enabled, there must be more data in the output buffer. Send the next byte - obj->tx_tail = (obj->tx_tail + 1) % TX_BUFFER_SIZE; - if (obj->tx_head == obj->tx_tail) - return -1; - - return 0; - } - -#else // STM32H7xx, has different uart_attach_tx_callback - - int HAL_HardwareSerial::_tx_complete_irq(serial_t *obj) { - // If interrupts are enabled, there must be more data in the output buffer. Send the next byte - obj->tx_tail = (obj->tx_tail + obj->tx_size) % TX_BUFFER_SIZE; - - if (obj->tx_head != obj->tx_tail) { - size_t remaining_data = (TX_BUFFER_SIZE + obj->tx_head - obj->tx_tail) % TX_BUFFER_SIZE; - obj->tx_size = min(remaining_data, (size_t)(TX_BUFFER_SIZE - obj->tx_tail)); - uart_attach_tx_callback(obj, _tx_complete_irq, obj->tx_size); - return -1; - } - return 0; - } + if (obj->tx_head == obj->tx_tail) return -1; -#endif + return 0; +} // Public Methods ////////////////////////////////////////////////////////////// @@ -331,37 +340,33 @@ void HAL_HardwareSerial::update_rx_head() { } #endif - #if ANY(STM32F2xx, STM32F4xx, STM32F7xx, STM32H7xx) + #if ANY(STM32F2xx, STM32F4xx, STM32F7xx) _serial.rx_head = RX_BUFFER_SIZE - RX_DMA.dma_streamRX->NDTR; #endif #if ANY(STM32F0xx, STM32F1xx) _serial.rx_head = RX_BUFFER_SIZE - RX_DMA.dma_channelRX->CNDTR; #endif + } int HAL_HardwareSerial::available() { update_rx_head(); - return ((unsigned int)(RX_BUFFER_SIZE + _serial.rx_head - _serial.rx_tail)) % RX_BUFFER_SIZE; } int HAL_HardwareSerial::peek() { update_rx_head(); - if (_serial.rx_head == _serial.rx_tail) - return -1; - + if (_serial.rx_head == _serial.rx_tail) return -1; return _serial.rx_buff[_serial.rx_tail]; } int HAL_HardwareSerial::read() { update_rx_head(); - if (_serial.rx_head == _serial.rx_tail) - return -1; // No chars if the head isn't ahead of the tail + if (_serial.rx_head == _serial.rx_tail) return -1; // No chars if the head isn't ahead of the tail unsigned char c = _serial.rx_buff[_serial.rx_tail]; _serial.rx_tail = (rx_buffer_index_t)(_serial.rx_tail + 1) % RX_BUFFER_SIZE; - return c; } @@ -375,18 +380,8 @@ size_t HAL_HardwareSerial::write(uint8_t c) { // Interrupt based wri _serial.tx_buff[_serial.tx_head] = c; _serial.tx_head = i; - #ifdef STM32H7xx // Support STM32H7xx with different uart_attach_tx_callback - if ((!serial_tx_active(&_serial)) && (_serial.tx_head != _serial.tx_tail)) { - size_t remaining_data = (TX_BUFFER_SIZE + _serial.tx_head -_serial.tx_tail) % TX_BUFFER_SIZE; - _serial.tx_size = min(remaining_data, (size_t)(TX_BUFFER_SIZE - _serial.tx_tail)); - uart_attach_tx_callback(&_serial, _tx_complete_irq, _serial.tx_size); - - return -1; - } - #else - if (!serial_tx_active(&_serial)) - uart_attach_tx_callback(&_serial, _tx_complete_irq); // Write next byte, launch interrupt - #endif + if (!serial_tx_active(&_serial)) + uart_attach_tx_callback(&_serial, _tx_complete_irq); // Write next byte, launch interrupt return 1; } @@ -395,144 +390,57 @@ void HAL_HardwareSerial::flush() { while ((_serial.tx_head != _serial.tx_tail)) { /* nada */ } // nop, the interrupt handler will free up space for us } -#if ANY(STM32F2xx, STM32F4xx, STM32F7xx, STM32H7xx) +#if ANY(STM32F2xx, STM32F4xx, STM32F7xx) void HAL_HardwareSerial::Serial_DMA_Read_Enable() { + RCC_AHB1PeriphClockCmd(RX_DMA.dma_rcc, ENABLE); // Enable DMA clock - if (RX_DMA.DMA_ID == 1) - __HAL_RCC_DMA1_CLK_ENABLE(); // Enable DMA1 clock - else - __HAL_RCC_DMA2_CLK_ENABLE(); // Enable DMA2 clock - - // Reset DMA, wait if needed to complete the running process - RX_DMA.dma_streamRX->CR = 0; // DMA stream clear/disable - while (RX_DMA.dma_streamRX->CR & DMA_SxCR_EN) { /* just wait for DMA to complete */ } - - // UART clear/disable - RX_DMA.uart->CR1 = 0; - - // Configure DMA - #if ANY(STM32F7xx, STM32H7xx) // F7 and H7 use RDR (Receive Data Register) - RX_DMA.dma_streamRX->PAR = (uint32_t)(&RX_DMA.uart->RDR); // DMA stream Peripheral Address Register = USART Data Register + #ifdef STM32F7xx + RX_DMA.dma_streamRX->PAR = (uint32_t)(&RX_DMA.uart->RDR); // RX peripheral receive address (usart) F7 #else - RX_DMA.dma_streamRX->PAR = (uint32_t)(&RX_DMA.uart->DR); // DMA stream Peripheral Address Register = USART Data Register + RX_DMA.dma_streamRX->PAR = (uint32_t)(&RX_DMA.uart->DR); // RX peripheral address (usart) F2 / F4 #endif + RX_DMA.dma_streamRX->M0AR = (uint32_t)_serial.rx_buff; // RX destination address (memory) + RX_DMA.dma_streamRX->NDTR = RX_BUFFER_SIZE; // RX buffer size - RX_DMA.dma_streamRX->M0AR = (uint32_t)_serial.rx_buff; // DMA stream Memory 0 Adress Register = RX buffer address - RX_DMA.dma_streamRX->NDTR = RX_BUFFER_SIZE; // DMA stream Number of Data Transfer Register - - #if DISABLED(STM32H7xx) // Select channel via CR register - - RX_DMA.dma_streamRX->CR = 4 << DMA_SxCR_CHSEL_Pos; // DMA stream Channel Selection, always use channel 4 - - #else // STM32H7xx, select channel with DMAMUX1, channel DMA1 is channel DMAMUX, channel DMA2 is channel DMAMUX + 8 - - if (RX_DMA.uart == USART1) - DMAMUX1_Channel10->CCR |= DMA_REQUEST_USART1_RX; // DMA2, Stream 2 + RX_DMA.dma_streamRX->CR = (RX_DMA.dma_channel << 25); // RX channel selection, set to 0 all the other CR bits - if (RX_DMA.uart == USART2) - DMAMUX1_Channel5->CCR |= DMA_REQUEST_USART2_RX; // DMA1, Stream 5 + RX_DMA.dma_streamRX->CR |= (3 << 16); // RX priority level: Very High - if (RX_DMA.uart == USART3) - DMAMUX1_Channel1->CCR |= DMA_REQUEST_USART3_RX; // DMA1, Stream 1 - #ifdef UART4 - if (RX_DMA.uart == UART4) - DMAMUX1_Channel2->CCR |= DMA_REQUEST_UART4_RX; // DMA1, Stream 2 - #endif - - #ifdef USART4 - if (RX_DMA.uart == USART4) - DMAMUX1_Channel2->CCR |= DMA_REQUEST_USART4_RX; // DMA1, Stream 2 - #endif - - #ifdef UART5 - if (RX_DMA.uart == UART5) - DMAMUX1_Channel0->CCR |= DMA_REQUEST_UART5_RX; // DMA1, Stream 0 - #endif - - #ifdef USART6 - if (RX_DMA.uart == USART6) - DMAMUX1_Channel9->CCR |= DMA_REQUEST_USART6_RX; // DMA2, Stream 1 - #endif - - #endif // !STM32H7xx - - // Configure DMA - //RX_DMA.dma_streamRX->CR |= DMA_MBURST_SINGLE; // DMA stream Memory Burst transfer: single transfer = 0b00 - //RX_DMA.dma_streamRX->CR |= DMA_PBURST_SINGLE; // DMA stream Peripheral Burst transfer: single transfer = 0b00 - - #if ENABLED(STM32H7xx) - RX_DMA.dma_streamRX->CR |= DMA_SxCR_TRBUFF; // DMA stream Transfer handle bufferable (required for UART/USART) - #endif - - //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_CT; // DMA stream Current Target (only in double buffer mode) - //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_DBM; // DMA stream Double Buffer Mode - //RX_DMA.dma_streamRX->CR |= DMA_PRIORITY_LOW; // DMA stream Priority Level Low = 0b00 - //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_PINCOS; // DMA stream Peripheral Increment Offset Size - //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_MSIZE; // DMA stream Memory data Size: 8 bit = 0b00 - //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_PSIZE; // DMA stream Peripheral data Size: 8 bit = 0b00 - RX_DMA.dma_streamRX->CR |= DMA_SxCR_MINC; // DMA stream Memory Increment enable - //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_PINC; // DMA stream Peripheral increment - RX_DMA.dma_streamRX->CR |= DMA_SxCR_CIRC; // DMA stream Circular mode enable - //RX_DMA.dma_streamRX->CR |= DMA_PERIPH_TO_MEMORY; // DMA stream transfer Direction: Peripheral-to-memory = b00 - //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_PFCTRL; // DMA stream Peripheral Flow Controller: DMA = 0 - //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_TCIE; // DMA stream Transfer Complete Interrupt - //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_HTIE; // DMA stream Half Transfer Interrupt - //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_TEIE; // DMA stream Transfer Error Interrupt - //RX_DMA.dma_streamRX->CR &= ~DMA_SxCR_DMEIE; // DMA stream Direct Mode Error Interrupt - RX_DMA.dma_streamRX->CR |= DMA_SxCR_EN; // DMA stream Enable - - // Configure UART/USART - RX_DMA.uart->CR3 |= USART_CR3_DMAR; // UART DMA Receiver - RX_DMA.uart->CR1 |= USART_CR1_TE; // UART Transmitter Enable - RX_DMA.uart->CR1 |= USART_CR1_RE; // UART Receiver Enable - RX_DMA.uart->CR1 |= USART_CR1_UE; // UART Enable + //RX_DMA.dma_streamRX->CR &= ~(3 << 13); // RX memory data size: 8 bit + //RX_DMA.dma_streamRX->CR &= ~(3 << 11); // RX peripheral data size: 8 bit + RX_DMA.dma_streamRX->CR |= (1 << 10); // RX memory increment mode + //RX_DMA.dma_streamRX->CR &= ~(1 << 9); // RX peripheral no increment mode + RX_DMA.dma_streamRX->CR |= (1 << 8); // RX circular mode enabled + //RX_DMA.dma_streamRX->CR &= ~(1 << 6); // RX data transfer direction: Peripheral-to-memory + RX_DMA.uart->CR3 |= (1 << 6); // Enable DMA receiver (DMAR) + RX_DMA.dma_streamRX->CR |= (1 << 0); // RX enable DMA } -#endif // STM32F2xx || STM32F4xx || STM32F7xx || STM32H7xx +#endif // STM32F2xx || STM32F4xx || STM32F7xx #if ANY(STM32F0xx, STM32F1xx) void HAL_HardwareSerial::Serial_DMA_Read_Enable() { + RCC_AHBPeriphClockCmd(RX_DMA.dma_rcc, ENABLE); // enable DMA clock - if (RX_DMA.DMA_ID == 1) - __HAL_RCC_DMA1_CLK_ENABLE(); // enable DMA1 clock - else - __HAL_RCC_DMA2_CLK_ENABLE(); // enable DMA2 clock + RX_DMA.dma_channelRX->CPAR = (uint32_t)(&RX_DMA.uart->DR); // RX peripheral address (usart) + RX_DMA.dma_channelRX->CMAR = (uint32_t)_serial.rx_buff; // RX destination address (memory) + RX_DMA.dma_channelRX->CNDTR = RX_BUFFER_SIZE; // RX buffer size - RX_DMA.dma_channelRX->CCR &= ~USART_CR1_UE; // DMA stream clear/disable - while (RX_DMA.dma_channelRX->CCR & DMA_CCR_EN) { /* just wait for DMA to complete */ } - - // Clear/disable UART and DMA - RX_DMA.uart->CR1 = 0; // UART clear CR1, disable DMA + RX_DMA.dma_channelRX->CCR = 0; // RX channel selection, set to 0 all the other CR bits - // Configure DMA + RX_DMA.dma_channelRX->CCR |= (3<<12); // RX priority level: Very High - #ifdef STM32F0xx - RX_DMA.dma_channelRX->CPAR = (uint32_t)(&RX_DMA.uart->RDR); // DMA channel Peripheral Address Register = USART Data Register - #else - RX_DMA.dma_channelRX->CPAR = (uint32_t)(&RX_DMA.uart->DR); // DMA channel Peripheral Address Register = USART Data Register - #endif + //RX_DMA.dma_channelRX->CCR &= ~(1<<10); // RX memory data size: 8 bit + //RX_DMA.dma_channelRX->CCR &= ~(1<<8); // RX peripheral data size: 8 bit + RX_DMA.dma_channelRX->CCR |= (1<<7); // RX memory increment mode + //RX_DMA.dma_channelRX->CCR &= ~(1<<6); // RX peripheral no increment mode + RX_DMA.dma_channelRX->CCR |= (1<<5); // RX circular mode enabled + //RX_DMA.dma_channelRX->CCR &= ~(1<<4); // RX data transfer direction: Peripheral-to-memory - RX_DMA.dma_channelRX->CMAR = (uint32_t)_serial.rx_buff; // DMA channel Memory Address Register - RX_DMA.dma_channelRX->CNDTR = RX_BUFFER_SIZE; // DMA channel Number of Data Transfer Register - //RX_DMA.dma_channelRX->CCR |= (0b00 << DMA_CCR_PL_Pos); // DMA channel Priority Level: Low = 0b00 - //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_MSIZE; // DMA channel Data Size: 8 bit = 0 - //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_PSIZE; // DMA channel Peripheral data size: 8 bit = 0 - RX_DMA.dma_channelRX->CCR |= DMA_CCR_MINC; // DMA channel Memory Increment enable - //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_PINC; // DMA channel Peripheral Increment disable - RX_DMA.dma_channelRX->CCR |= DMA_CCR_CIRC; // DMA channel Circular mode enable - //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_DIR; // DMA channel Data Transfer direction: 0=Read peripheral, 1=Read memory - //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_TEIE; // DMA channel Transfer Error Interrupt - //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_HTIE; // DMA channel Half Transfer Interrupt - //RX_DMA.dma_channelRX->CCR &= ~DMA_CCR_TCIE; // DMA channel Transfer Complete Interrupt - RX_DMA.dma_channelRX->CCR |= DMA_CCR_EN; // DMA channel enable - - // Configure UART/USART - RX_DMA.uart->CR3 |= USART_CR3_DMAR; // UART DMA Receiver enabled - RX_DMA.uart->CR1 |= USART_CR1_TE; // UART Transmitter Enable - RX_DMA.uart->CR1 |= USART_CR1_RE; // UART Receiver Enable - RX_DMA.uart->CR1 |= USART_CR1_UE; // UART Enable + RX_DMA.uart->CR3 |= (1<<6); // enable DMA receiver (DMAR) + RX_DMA.dma_channelRX->CCR |= (1<<0); // RX enable DMA } #endif // STM32F0xx || STM32F1xx diff --git a/Marlin/src/HAL/STM32/HardwareSerial.h b/Marlin/src/HAL/STM32/HardwareSerial.h index cf976372782a..cc564322b4d5 100644 --- a/Marlin/src/HAL/STM32/HardwareSerial.h +++ b/Marlin/src/HAL/STM32/HardwareSerial.h @@ -38,10 +38,12 @@ typedef struct { USART_TypeDef * uart; - uint32_t DMA_ID; // DMA1=1; DM2=2; BDMA=3 + uint32_t dma_rcc; #if ANY(STM32F0xx, STM32F1xx) // F0 / F1 + DMA_TypeDef * dma_controller; DMA_Channel_TypeDef * dma_channelRX; - #else // F2 / F4 / F7 / H7 + #else // F2 / F4 / F7 + uint32_t dma_channel; DMA_Stream_TypeDef * dma_streamRX; #endif } DMA_CFG; diff --git a/Marlin/src/HAL/STM32/eeprom_flash.cpp b/Marlin/src/HAL/STM32/eeprom_flash.cpp index d649775d45cb..37963ad50154 100644 --- a/Marlin/src/HAL/STM32/eeprom_flash.cpp +++ b/Marlin/src/HAL/STM32/eeprom_flash.cpp @@ -74,24 +74,12 @@ #define EEPROM_SLOTS ((FLASH_UNIT_SIZE) / (MARLIN_EEPROM_SIZE)) #define SLOT_ADDRESS(slot) (FLASH_ADDRESS_START + (slot * (MARLIN_EEPROM_SIZE))) -#ifdef STM32H7xx - #define FLASHWORD_SIZE 32U // STM32H7xx a FLASHWORD is 32 bytes (256 bits) - #define UNLOCK_FLASH() if (!flash_unlocked) { \ - HAL_FLASH_Unlock(); \ - __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | \ - FLASH_FLAG_PGSERR); \ - flash_unlocked = true; \ - } -#else - #define FLASHWORD_SIZE 4U // STM32F4xx a FLASHWORD is 4 bytes sizeof(uint32_t) #define UNLOCK_FLASH() if (!flash_unlocked) { \ HAL_FLASH_Unlock(); \ __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | \ FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR); \ flash_unlocked = true; \ } -#endif - #define LOCK_FLASH() if (flash_unlocked) { HAL_FLASH_Lock(); flash_unlocked = false; } #define EMPTY_UINT32 ((uint32_t)-1) @@ -100,7 +88,7 @@ static uint8_t ram_eeprom[MARLIN_EEPROM_SIZE] __attribute__((aligned(4))) = {0}; static int current_slot = -1; - static_assert(0 == MARLIN_EEPROM_SIZE % FLASHWORD_SIZE, "MARLIN_EEPROM_SIZE must be a multiple of the FLASHWORD size"); // Ensure copying as uint32_t is safe + static_assert(0 == MARLIN_EEPROM_SIZE % 4, "MARLIN_EEPROM_SIZE must be a multiple of 4"); // Ensure copying as uint32_t is safe static_assert(0 == FLASH_UNIT_SIZE % MARLIN_EEPROM_SIZE, "MARLIN_EEPROM_SIZE must divide evenly into your FLASH_UNIT_SIZE"); static_assert(FLASH_UNIT_SIZE >= MARLIN_EEPROM_SIZE, "FLASH_UNIT_SIZE must be greater than or equal to your MARLIN_EEPROM_SIZE"); static_assert(IS_FLASH_SECTOR(FLASH_SECTOR), "FLASH_SECTOR is invalid"); @@ -159,12 +147,9 @@ bool PersistentStore::access_start() { bool PersistentStore::access_finish() { if (eeprom_data_written) { - - #if STM32H7xx + #ifdef STM32F4xx // MCU may come up with flash error bits which prevent some flash operations. // Clear flags prior to flash operations to prevent errors. - __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGSERR); - #else __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR); #endif @@ -209,17 +194,13 @@ bool PersistentStore::access_finish() { data = 0; bool success = true; - while (address < address_end) { - #if STM32H7xx - status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, address, uint32_t(ram_eeprom + offset)); - #else - memcpy(&data, ram_eeprom + offset, sizeof(data)); - status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data); - #endif + while (address < address_end) { + memcpy(&data, ram_eeprom + offset, sizeof(data)); + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data); if (status == HAL_OK) { - address += FLASHWORD_SIZE; - offset += FLASHWORD_SIZE; + address += sizeof(uint32_t); + offset += sizeof(uint32_t); } else { DEBUG_ECHOLNPGM("HAL_FLASH_Program=", status); diff --git a/Marlin/src/feature/accelerometer/acc_adxl345.cpp b/Marlin/src/feature/accelerometer/acc_adxl345.cpp deleted file mode 100644 index 2b061915d41d..000000000000 --- a/Marlin/src/feature/accelerometer/acc_adxl345.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/*************************************************************** - * - * ADXL345 3-AXIS ACCELEROMETER ON SPI BUS - * 4-WIRE SPI COMMUNICATION - * Define: SD_SS_PIN, SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN - * - ****************************************************************/ - -#include "../../inc/MarlinConfig.h" - -#if ENABLED(HAS_ADXL345_ACCELEROMETER) - -#include "acc_ADXL345.h" - -#include "../../MarlinCore.h" -#include "../../HAL/shared/Delay.h" - -#include "SPI.h" -extern SPIClass SPI; - -ADXL345 adxl345; - -void ADXL345::begin() { - pinMode(SD_SS_PIN, OUTPUT); // set ADXL345 Chip Select pin to output mode - pinMode(SD_SS_PIN, HIGH); // set ADXL345 Chip Select to HIGH before configuratin SPI - spiBegin(); // sets Chip Select to HIGH (again) - spiInit(SPI_HALF_SPEED); // calls SPI.begin(), sets speed to 4MHz, max is 5MHz for ADXL345 - SPI.setDataMode(SPI_MODE3); // ADXL345 uses SPI_MODE3 to communicate (CPOL=1, CPHA = 1) - - // set range to 2g (4wire SPI, right justify data, 10-bit resolution) - writeRegister(ADXL345_DATA_FORMAT_REG, ADXL345_DATA_RANGE_2G); - - // enable measurement mode, use streaming mode - writeRegister(ADXL345_POWER_CTL_REG, ADXL345_POWER_CTL_MEASURE | ADXL345_FIFO_CTL_MODE_STREAM); - - // set to 100Hz sampling rate - writeRegister(ADXL345_RATE_REG, ADXL345_RATE_100HZ); -} - -void ADXL345::end() { // put device in standby mode - writeRegister(ADXL345_POWER_CTL_REG, ADXL345_POWER_CTL_STANDBY); -} - -void ADXL345::writeRegister(uint8_t registerAddress, uint8_t data) { - digitalWrite(SD_SS_PIN, LOW); // set Chip Select to LOW to start the write - spiSend(registerAddress); // send the register address - spiSend(data); // send the data - digitalWrite(SD_SS_PIN, HIGH); // set Chip Select to HIGH to complete the write -} - -void ADXL345::readRegister(uint8_t registerAddress, int numBytes, uint8_t * buffer) { - uint8_t address = registerAddress | 0x80; // set read bit - if (numBytes > 1) - address = address | 0x40; // also set multi-byte read if needed - - digitalWrite(SD_SS_PIN, LOW); // set Chip Select to LOW to start the read - spiSend(address); // send the register address - for (int i = 0; i < numBytes; i++) - buffer[i] = spiRec(); // read the data - digitalWrite(SD_SS_PIN, HIGH); // set Chip Select to HIGH to complete the read - delayMicroseconds(5); // allow 5us for the FIFO/registers to update (see datasheet) -} - -// get a accleration measurement for the X, Y and Z axis -void ADXL345::readMeasurement(ADXL345_measurement_t *acceleration) { - readRegister(ADXL345_DATA_X0_REG, 6, (uint8_t*)acceleration); -} - -void ADXL345::select(const bool select) { - WRITE(SD_SS_PIN, !select); // ADXL345 is selected on LOW -} - -#endif // HAS_ADXL345_ACCELEROMETER \ No newline at end of file diff --git a/Marlin/src/feature/accelerometer/acc_adxl345.h b/Marlin/src/feature/accelerometer/acc_adxl345.h deleted file mode 100644 index d890cf2dd937..000000000000 --- a/Marlin/src/feature/accelerometer/acc_adxl345.h +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 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 - -// ADXL345 registers and defines -#define ADXL345_DEV_ID_REG 0x00 - -#define ADXL345_OFFSET_X_REG 0x1E // int8_t, 15.6mg/LSB (0x7F=2G) -#define ADXL345_OFFSET_Y_REG 0x1F // int8_t, 15.6mg/LSB (0x7F=2G) -#define ADXL345_OFFSET_Z_REG 0x20 // int8_t, 15.6mg/LSB (0x7F=2G) - -#define ADXL345_RATE_REG 0x2C // Sample rate register 0.1 - 3200Hz -#define ADXL345_RATE_0_1HZ 0x0 -#define ADXL345_RATE_0_2HZ 0x1 -#define ADXL345_RATE_0_39HZ 0x2 -#define ADXL345_RATE_0_78HZ 0x3 -#define ADXL345_RATE_1_56HZ 0x4 -#define ADXL345_RATE_3_13HZ 0x5 -#define ADXL345_RATE_6_25HZ 0x6 -#define ADXL345_RATE_12_5HZ 0x7 -#define ADXL345_RATE_25HZ 0x8 -#define ADXL345_RATE_50HZ 0x9 -#define ADXL345_RATE_100HZ 0xA -#define ADXL345_RATE_200HZ 0xB -#define ADXL345_RATE_400HZ 0xC -#define ADXL345_RATE_800HZ 0xD -#define ADXL345_RATE_1600HZ 0xE -#define ADXL345_RATE_3200HZ 0xF - -#define ADXL345_POWER_CTL_REG 0x2D // Power control register -#define ADXL345_POWER_CTL_STANDBY 0x00 -#define ADXL345_POWER_CTL_MEASURE 0x08 - -#define ADXL345_DATA_FORMAT_REG 0x31 // Data format register -#define ADXL345_DATA_SELFTEST_BIT 0x80 -#define ADXL345_DATA_3WIRE_BIT 0x40 -#define ADXL345_DATA_FULLRES_BIT 0x08 -#define ADXL345_DATA_MSB_BIT 0x04 -#define ADXL345_DATA_RANGE_MASK 3 -#define ADXL345_DATA_RANGE_2G 0 -#define ADXL345_DATA_RANGE_4G 1 -#define ADXL345_DATA_RANGE_8G 2 -#define ADXL345_DATA_RANGE_16G 3 - -#define ADXL345_DATA_X0_REG 0x32 // Measurement data registers -#define ADXL345_DATA_X1_REG 0x33 -#define ADXL345_DATA_Y0_REG 0x34 -#define ADXL345_DATA_Y1_REG 0x35 -#define ADXL345_DATA_Z0_REG 0x36 -#define ADXL345_DATA_Z1_REG 0x37 - -#define ADXL345_FIFO_CTL_REG 0x38 // FIFO control register -#define ADXL345_FIFO_CTL_MODE_BYPASS 0x00 -#define ADXL345_FIFO_CTL_MODE_ENABLED 0x40 -#define ADXL345_FIFO_CTL_MODE_STREAM 0x80 -#define ADXL345_FIFO_CTL_MODE_TRIGGER 0xC0 -#define ADXL345_FIFO_CTL_SAMPLE_COUNT_MASK 0x1F - -#define ADXL345_FIFO_STATUS_REG 0x39 // FIFO status register -#define ADXL345_FIFO_STATUS_ENTRIES_MASK 0x3F - -struct ADXL345_measurement_t { - int16_t x; - int16_t y; - int16_t z; -}; - -class ADXL345 { - public: - void begin(); - void end(); - void readMeasurement(ADXL345_measurement_t * acceleration); - void writeRegister(uint8_t registerAddress, uint8_t data); - void readRegister(uint8_t registerAddress, int numBytes, uint8_t * buffer); - private: - void select(const bool select); -}; - -extern ADXL345 adxl345; \ No newline at end of file diff --git a/ini/features.ini b/ini/features.ini index 4d6ad7b55641..b2e9d6884e75 100644 --- a/ini/features.ini +++ b/ini/features.ini @@ -12,7 +12,6 @@ # The order of the features matters for source-filter resolution inside of common-dependencies.py. [features] -HAS_ADXL345_ACCELEROMETER = build_src_filter=+ YHCB2004 = LiquidCrystal_AIP31068=https://github.com/ellensp/LiquidCrystal_AIP31068/archive/3fc43b7.zip, red-scorp/SoftSPIB@^1.1.1 HAS_TFT_LVGL_UI = lvgl=https://github.com/makerbase-mks/LVGL-6.1.1-MKS/archive/a3ebe98bc6.zip build_src_filter=+ From 74a17cf5c0ba663c84fefac3a4b13193a8f71381 Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Sat, 11 Jan 2025 18:18:02 +0800 Subject: [PATCH 18/21] Small improvements and cleanup Prevent a condition where the host could send a configuration, while the toolhead is rebooting, which could cause the configuration to arrive incomplete. --- Marlin/src/HAL/STM32/CAN_host.cpp | 49 +++++++-------- Marlin/src/HAL/STM32/FDCAN_host.cpp | 79 +++++++------------------ Marlin/src/HAL/STM32/FDCAN_toolhead.cpp | 32 +++++----- 3 files changed, 59 insertions(+), 101 deletions(-) diff --git a/Marlin/src/HAL/STM32/CAN_host.cpp b/Marlin/src/HAL/STM32/CAN_host.cpp index e86a792fe027..3af090be1089 100644 --- a/Marlin/src/HAL/STM32/CAN_host.cpp +++ b/Marlin/src/HAL/STM32/CAN_host.cpp @@ -60,8 +60,6 @@ extern "C" void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan); // C extern "C" void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan); // CAN FIFO1 new message callback extern "C" void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan); // CAN error interrupt callback -//#define CAN_LED_PIN PC8 - #ifndef CAN_BAUDRATE #define CAN_BAUDRATE 1000000 #endif @@ -70,9 +68,6 @@ extern "C" void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan); // C #error ERROR: Select a valid CAN_BAUDRATE: 1000000, 500000, 250000 or 125000 baud #endif -#define STDID_FIFO_TOGGLE_BIT 0b10000000000 -#define EXTID_FIFO_TOGGLE_BIT 0x10000000 - #define CAN_HOST_FIFO_DEPTH 3 // RX and TX FIFO0 and FIFO1 depth CAN_HandleTypeDef hCAN1 = { 0 }; // The global CAN handle @@ -88,11 +83,10 @@ volatile uint32_t CAN_host_error_code = 0; // Store the CAN hos volatile bool first_E0_error = true; // First CAN bus error, show warning only once volatile bool string_message_complete = false; // Signals a complete string message was received -volatile uint32_t string_message_index = 0; // Index into the CAN string that is being received uint32_t CAN_next_temp_report_time = 12000; // Track when the next toolhead temperature report should have arrived uint32_t CAN_next_error_message_time = 0; // Track when to display the next repeat of an error message volatile bool CAN_host_FIFO_toggle_bit = false; // FIFO toggle flag for receiver FIFO filtering -char string_message[CAN_HOST_MAX_STRING_MSG_LENGTH] = "\0"; // CAN string message buffer for incoming messages +SString string_message; // CAN string message buffer for incoming messages uint32_t CAN_host_get_iostate() { return CAN_io_state; @@ -100,7 +94,7 @@ uint32_t CAN_host_get_iostate() { uint32_t CAN_set_extended_id(int gcode_type, int gcode_no, int parameter1, int parameter2, int count) { - CAN_host_FIFO_toggle_bit = !CAN_host_FIFO_toggle_bit; // FIFO toggle bit + CAN_host_FIFO_toggle_bit = !CAN_host_FIFO_toggle_bit; // FIFO toggle bit return (CAN_host_FIFO_toggle_bit ? EXTID_FIFO_TOGGLE_BIT : 0) | // FIFO toggle bit (count << CAN_ID_PARAMETER_COUNT_BIT_POS) | // Parameter count @@ -157,7 +151,7 @@ HAL_StatusTypeDef CAN_host_send_gcode_2params(uint32_t Gcode_type, uint32_t Gcod Gcode_type = CAN_ID_GCODE_TYPE_M; #if ENABLED(CAN_DEBUG) - SERIAL_ECHOPGM("; CAN TO TOOLHEAD: \"M", Gcode_no); + SERIAL_ECHOPGM("; MSG to toolhead: \"M", Gcode_no); if (parameter1) { SERIAL_CHAR(' ', parameter1); if (value1 == int(value1)) @@ -214,7 +208,7 @@ HAL_StatusTypeDef CAN_host_send_gcode_2params(uint32_t Gcode_type, uint32_t Gcod return status; } -void CAN_host_send_setup() { // Send setup to toolhead +void CAN_host_send_setup(bool changeStatus) { // Send setup to toolhead // NOTE: Sending many command too fast will cause a Marlin command buffer overrun at the toolhead, add delays if needed CAN_toolhead_setup_request = false; @@ -304,8 +298,9 @@ void CAN_host_send_setup() { // Send setup to toolhead CAN_host_send_gcode_2params('M', 710, 'E', controllerFan.settings.extruder_auto_fan_speed, 'P', controllerFan.settings.probing_auto_fan_speed); #endif - // Signals to the toolhead that the configuration is complete, use it as the last Gcode to send - CAN_host_send_gcode_2params('M', CAN_HOST_CONFIGURATION_COMPLETE, 0, 0, 0, 0); + // Signal to the toolhead that the configuration is complete, use it as the last Gcode to send + if (changeStatus) + CAN_host_send_gcode_2params('M', CAN_HOST_CONFIGURATION_COMPLETE, 0, 0, 0, 0); } void CAN_host_idle() { // Tasks that can/should not be done in the ISR @@ -315,15 +310,14 @@ void CAN_host_idle() { // Tasks that can/should not be done in the ISR CAN_host_send_timestamp(); } - if (string_message_complete) { // Received string message is complete, show the string + if (string_message_complete) { // Received string message is complete, display the string BUZZ(1, SOUND_OK); SERIAL_ECHOPGM(">>> CAN toolhead MSG: "); - for (uint32_t i = 0; i < string_message_index; i++) - SERIAL_CHAR(string_message[i]); // Show received string message, ends on '\n' + string_message.echo(); // Show received string message, ends on '\n' string_message_complete = false; // Get ready for the next string - string_message_index = 0; + string_message.clear(); } // Report time sync results @@ -365,7 +359,7 @@ void CAN_host_idle() { // Tasks that can/should not be done in the ISR #endif if (CAN_toolhead_setup_request) // The toolhead requested the setup configuration - CAN_host_send_setup(); + CAN_host_send_setup(true); } } @@ -722,16 +716,13 @@ void CAN_host_read_message(CAN_HandleTypeDef *hcan, uint32_t RxFifo) { // ISR! F endstops.update(); } - if (RxHeader.StdId & CAN_ID_STRING_MESSAGE_MASK) { // Toolhead sends a string message - char * CAN_RX_p = (char *)CAN_RX_buffer_FIFO; - for (uint32_t i = 0; i < RxHeader.DLC; i++) { - string_message[string_message_index++ % CAN_HOST_MAX_STRING_MSG_LENGTH] = CAN_RX_p[i]; // Copy message to global string buffer + if (RxHeader.StdId & CAN_ID_STRING_MESSAGE_BIT_MASK) { // Toolhead sent a string message + char *CAN_RX_p = (char *)CAN_RX_buffer_FIFO; + for (uint32_t i = 0; i < RxHeader.DLC; i++) + string_message.append(CAN_RX_p[i]); // Copy message to global buffer - if (CAN_RX_p[i] == '\n') { - string_message_complete = true; // String is complete, idle task can show the string - string_message[string_message_index % CAN_HOST_MAX_STRING_MSG_LENGTH] = 0; // Close string with \0 - } - } + if (CAN_RX_p[RxHeader.DLC - 1] == '\n') + string_message_complete = true; // String is complete, idle task can show the string } else if (RxHeader.DLC == 4) { // Only 1 record, so it's a temperature update (DLC = Data Length Code is 4 bytes) float *fp = (float *)CAN_RX_buffer_FIFO; @@ -740,14 +731,14 @@ void CAN_host_read_message(CAN_HandleTypeDef *hcan, uint32_t RxFifo) { // ISR! F first_E0_error = true; // Reset error status } - if (RxHeader.StdId & CAN_ID_REQUEST_TIME_SYNC_MASK) { // Toolhead signals request for time stamp + if (RxHeader.StdId & CAN_ID_REQUEST_TIME_SYNC_BIT_MASK) { // Toolhead signals request for time stamp time_sync_request_time = micros(); // Record the time sync request receive time CAN_time_sync_request = true; } - CAN_toolhead_setup_request = (RxHeader.StdId & CAN_ID_REQUEST_SETUP_MASK) > 0; // Toolhead requests setup configuration + CAN_toolhead_setup_request = (RxHeader.StdId & CAN_ID_REQUEST_SETUP_BIT_MASK) > 0; // Toolhead requests setup configuration - CAN_toolhead_error = (RxHeader.StdId & CAN_ID_ERROR_MASK) > 0; // Toolhead signals an error + CAN_toolhead_error = (RxHeader.StdId & CAN_ID_ERROR_BIT_MASK) > 0; // Toolhead signals an error } } diff --git a/Marlin/src/HAL/STM32/FDCAN_host.cpp b/Marlin/src/HAL/STM32/FDCAN_host.cpp index 772b66175862..e3eef93818aa 100644 --- a/Marlin/src/HAL/STM32/FDCAN_host.cpp +++ b/Marlin/src/HAL/STM32/FDCAN_host.cpp @@ -56,38 +56,10 @@ extern "C" void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t #error ERROR: Select a valid CAN_BAUDRATE: 1000000, 500000, 250000 or 125000 baud #endif -//#define CAN_HOST_IO_MASK 0b11111 // Masks for the 5 virtual IO bits (see below) -//#define CAN_HOST_GCODE_NUMBER_MASK 0b1111111111111 -//#define CAN_HOST_PARAMETER_MASK 0b11111 -//#define CAN_HOST_PARAMETER_COUNT_MASK 0b111 -//#define CAN_HOST_GCODE_TYPE_MASK 0b11 - -//#define CAN_HOST_PARAMETER1_BIT_POS 0 -//#define CAN_HOST_PARAMETER2_BIT_POS 5 -//#define CAN_HOST_GCODE_NUMBER_BIT_POS 10 -//#define CAN_HOST_GCODE_TYPE_BIT_POS 23 -//#define CAN_HOST_PARAMETER_COUNT_BIT_POS 25 - -//#define CAN_HOST_GCODE_TYPE_D 0 -//#define CAN_HOST_GCODE_TYPE_G 1 -//#define CAN_HOST_GCODE_TYPE_M 2 -//#define CAN_HOST_GCODE_TYPE_T 3 - -//#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 FDCAN_HOST_TX_FIFO_DEPTH 8 // TX FIFO0 and FIFO1 depth 0-32 -#define FDCAN_HOST_RX_FIFO_DEPTH 8 // RX FIFO0 and FIFO1 depth 0-64 -//#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 FDCAN_HOST_DATALENGTH_OFFSET 16 // Bit offset of FDCAN_DLC_BYTES_1 in register - -//#define CAN_ERROR_RX_FIFO_OVERFLOW (1 << 0) // Incoming message lost -//#define CAN_ERROR_TX_MSG_DROPPED (1 << 1) // Outgoing message dropped -//#define CAN_ERROR_INVALID_GCODE (1 << 2) // Gcode could not be sent over CANBUS -//#define CAN_ERROR_INVALID_BAUDRATE (1 << 3) // Generated baudrate doesn't match CAN_BAUDRATE +#define FDCAN_HOST_TX_FIFO_DEPTH 8 // TX FIFO0 and FIFO1 depth 0-32 +#define FDCAN_HOST_RX_FIFO_DEPTH 8 // RX FIFO0 and FIFO1 depth 0-64 + +#define FDCAN_HOST_DATALENGTH_OFFSET 16 // Bit offset of FDCAN_DLC_BYTES_1 in register FDCAN_HandleTypeDef hCAN1 = { 0 }; // The global FDCAN handle FDCAN_TxHeaderTypeDef TxHeader = { 0 }; // Header to send a FDCAN message @@ -102,11 +74,10 @@ volatile uint32_t CAN_host_error_code = 0; // Store the CAN hos volatile bool first_E0_error = true; // First CAN bus error, show warning only once volatile bool string_message_complete = false; // Signals a complete string message was received -volatile uint32_t string_message_index = 0; // Index into the CAN string that is being received -uint32_t CAN_next_temp_report_time = 12000; // Track when the next toolhead temperature report should have arrived +uint32_t CAN_next_temp_report_time = 10000; // Track when the next toolhead temperature report should arrive, delay at startup uint32_t CAN_next_error_message_time = 0; // Track when to display the next repeat of an error message volatile bool CAN_host_FIFO_toggle_bit = false; // FIFO toggle flag for receiver FIFO filtering -char string_message[CAN_HOST_MAX_STRING_MSG_LENGTH] = "\0"; // CAN string message buffer for incoming messages +SString string_message; // CAN string message buffer for incoming messages uint32_t CAN_host_get_iostate() { return CAN_io_state; @@ -225,7 +196,7 @@ HAL_StatusTypeDef CAN_host_send_gcode_2params(uint32_t Gcode_type, uint32_t Gcod return status; } -void CAN_host_send_setup() { // Send setup to toolhead +void CAN_host_send_setup(bool changeStatus) { // Send setup to toolhead // NOTE: Sending many command too fast will cause a Marlin command buffer overrun at the toolhead, add delays if needed CAN_toolhead_setup_request = false; @@ -316,7 +287,8 @@ void CAN_host_send_setup() { // Send setup to toolhead #endif // Signal to the toolhead that the configuration is complete, use it as the last Gcode to send - CAN_host_send_gcode_2params('M', CAN_HOST_CONFIGURATION_COMPLETE, 0, 0, 0, 0); + if (changeStatus) + CAN_host_send_gcode_2params('M', CAN_HOST_CONFIGURATION_COMPLETE, 0, 0, 0, 0); } void CAN_host_idle() { // Tasks that can/should not be done in the ISR @@ -326,18 +298,14 @@ void CAN_host_idle() { // Tasks that can/should not be done in the ISR CAN_host_send_timestamp(); } - if (CAN_toolhead_setup_request) // The toolhead requested the setup configuration - CAN_host_send_setup(); - if (string_message_complete) { // Received string message is complete, display the string BUZZ(1, SOUND_OK); SERIAL_ECHOPGM(">>> CAN toolhead MSG: "); - for (uint32_t i = 0; i < string_message_index; i++) - SERIAL_CHAR(string_message[i]); // Show received string message, ends on '\n' + string_message.echo(); // Show received string message, ends on '\n' string_message_complete = false; // Get ready for the next string - string_message_index = 0; + string_message.clear(); } // Report time sync results @@ -379,7 +347,7 @@ void CAN_host_idle() { // Tasks that can/should not be done in the ISR #endif if (CAN_toolhead_setup_request) // The toolhead requested the setup configuration - CAN_host_send_setup(); + CAN_host_send_setup(true); } } @@ -790,32 +758,29 @@ void FDCAN_host_read_message(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo) { // endstops.update(); } - if (RxHeader.Identifier & CAN_ID_STRING_MESSAGE_MASK) { // Toolhead sent a string message - char * CAN_RX_p = (char *)CAN_RX_buffer_FIFO; - for (uint32_t i = 0; i < (RxHeader.DataLength >> FDCAN_HOST_DATALENGTH_OFFSET); i++) { - string_message[string_message_index++ % CAN_HOST_MAX_STRING_MSG_LENGTH] = CAN_RX_p[i]; // Copy message to global buffer + if (RxHeader.Identifier & CAN_ID_STRING_MESSAGE_BIT_MASK) { // Toolhead sent a string message + char *CAN_RX_p = (char *)CAN_RX_buffer_FIFO; + for (uint32_t i = 0; i < (RxHeader.DataLength >> FDCAN_HOST_DATALENGTH_OFFSET); i++) + string_message.append(CAN_RX_p[i]); // Copy message to global buffer - if (CAN_RX_p[i] == '\n') { - string_message_complete = true; // String is complete, idle task can show the string - string_message[string_message_index % CAN_HOST_MAX_STRING_MSG_LENGTH] = 0; // Close string with \0 - } - } + if (CAN_RX_p[(RxHeader.DataLength >> FDCAN_HOST_DATALENGTH_OFFSET) - 1] == '\n') + string_message_complete = true; // String is complete, idle task can show the string } else if (RxHeader.DataLength == FDCAN_DLC_BYTES_4) { // Only 1 record, so it's a temperature update (DLC = Data Length Code == 4 bytes) float *fp = (float *)CAN_RX_buffer_FIFO; thermalManager.temp_hotend[0].celsius = *fp; // Set E0 hotend temperature from received message CAN_next_temp_report_time = millis() + CAN_HOST_E0_TEMP_UPDATE_WATCHDOG_TIME; // A temp update must be received within this window - first_E0_error = true; // Reset error status + first_E0_error = true; // Reset error status } - if (RxHeader.Identifier & CAN_ID_REQUEST_TIME_SYNC_MASK) { // Toolhead signals request for time stamp + if (RxHeader.Identifier & CAN_ID_REQUEST_TIME_SYNC_BIT_MASK) { // Toolhead signals request for time stamp time_sync_request_time = micros(); // Record the time sync request receive time CAN_time_sync_request = true; } - CAN_toolhead_setup_request = (RxHeader.Identifier & CAN_ID_REQUEST_SETUP_MASK) > 0; // Toolhead requests setup configuration + CAN_toolhead_setup_request = (RxHeader.Identifier & CAN_ID_REQUEST_SETUP_BIT_MASK) > 0; // Toolhead requests setup configuration - CAN_toolhead_error = (RxHeader.Identifier & CAN_ID_ERROR_MASK) > 0; // Toolhead signals an error + CAN_toolhead_error = (RxHeader.Identifier & CAN_ID_ERROR_BIT_MASK) > 0; // Toolhead signals an error } } diff --git a/Marlin/src/HAL/STM32/FDCAN_toolhead.cpp b/Marlin/src/HAL/STM32/FDCAN_toolhead.cpp index 5a00d8e6e14d..66d4778a768c 100644 --- a/Marlin/src/HAL/STM32/FDCAN_toolhead.cpp +++ b/Marlin/src/HAL/STM32/FDCAN_toolhead.cpp @@ -86,10 +86,10 @@ struct CAN_MSG_BUFFER { // Struct to hold a CAN message SString CAN_string_buffer; // String buffer to hold outgoing string messages char gcode_type[4] = { 'D', 'G', 'M', 'T' }; // The 4 Gcode types -bool CAN_toolhead_not_configured = true; // Track if the host sent the required toolhead configuration +volatile bool CAN_toolhead_not_configured = true; // Track if the host sent the required toolhead configuration uint32_t CAN_toolhead_error_code = 0; // Signals an CAN error occured, report to host uint32_t CAN_previous_error_code = 0; // Remember last error code so changes can be detected -uint32_t CAN_next_temp_report_time = 0; // The next temperature report time +uint32_t CAN_next_temp_report_time = 0; // The next temperature report time, delay on startup uint32_t CAN_send_next_string_part_time = 0; // Send the next part of a string message volatile bool CAN_FIFO_toggle_bit = 0; // FIFO toggle bit for receiver filtering to FIFO0 and FIFO1 bool CAN_request_time_sync = false; // Request a timestamp for NTP time sync @@ -106,10 +106,11 @@ volatile uint32_t CAN_queue_head = 0; // Queue head index volatile uint32_t CAN_queue_tail = 0; // Queue tail index // Function to calculate the CAN message ID holding various bits of information -uint32_t CAN_get_virtual_IO() { +uint32_t CAN_get_virtual_IO(bool tempUpdate) { CAN_FIFO_toggle_bit = !CAN_FIFO_toggle_bit; // Toggle FIFO bit for receiver filtering to FIFO0 and FIFO1 + bool configured = tempUpdate && CAN_toolhead_not_configured; // Only request setup during temp updates, not IO interrupts return ( #ifdef Z_MIN_PROBE_PIN @@ -128,9 +129,9 @@ uint32_t CAN_get_virtual_IO() { (READ(FILAMENT_RUNOUT_PIN) << CAN_ID_FILAMENT_BIT_POS) | // Report filament detector status #endif - (CAN_FIFO_toggle_bit ? STDID_FIFO_TOGGLE_BIT : 0) | // Add FIFO toggle bit - (CAN_toolhead_not_configured << CAN_ID_REQUEST_SETUP_BIT_POS) | // Request toolhead setup configuration - ((!!CAN_toolhead_error_code) << CAN_ID_ERROR_BIT_POS) // Report error (if any) + (CAN_FIFO_toggle_bit ? STDID_FIFO_TOGGLE_BIT : 0) | // Add FIFO toggle bit + (configured << CAN_ID_REQUEST_SETUP_BIT_POS) | // Request toolhead setup configuration + ((!!CAN_toolhead_error_code) << CAN_ID_ERROR_BIT_POS) // Report error (if any) ); } @@ -278,12 +279,14 @@ void TIM16_IRQHandler(void) { // ISR! Combined TIM16 and CAN interrupt handler ( // OR // // Call the required callbacks directly, faster but limited error reporting - if (hCAN1.Instance->IR & FDCAN_IR_RF0N) { // New FIFO0 CAN message + // New FIFO0 CAN message + if (hCAN1.Instance->IR & FDCAN_IR_RF0N) { __HAL_FDCAN_CLEAR_FLAG(&hCAN1, FDCAN_IR_RF0N); CAN_receive_msg(FDCAN_RX_FIFO0); } - if (hCAN1.Instance->IR & FDCAN_IR_RF1N) { // New FIFO1 CAN message + // New FIFO1 CAN message + if (hCAN1.Instance->IR & FDCAN_IR_RF1N) { __HAL_FDCAN_CLEAR_FLAG(&hCAN1, FDCAN_IR_RF1N); CAN_receive_msg(FDCAN_RX_FIFO1); } @@ -469,8 +472,7 @@ HAL_StatusTypeDef CAN_toolhead_start(void) { // Start the CAN device } // CAN_toolhead_start // Send an IO status update to the host, and the E0 temperature if requested -void CAN_toolhead_send_update(bool TempUpdate) { // Called from temperature ISR! - +void CAN_toolhead_send_update(bool tempUpdate) { // Called from temperature ISR! // Send a IO/temp report from the toolhead to the host FDCAN_TxHeaderTypeDef CanTxHeader; uint8_t can_tx_buffer[8]; // Transmit FDCAN message buffer @@ -484,9 +486,9 @@ void CAN_toolhead_send_update(bool TempUpdate) { // Called from temperature ISR! CanTxHeader.IdType = FDCAN_STANDARD_ID; // FDCAN_STANDARD_ID / FDCAN_EXTENDED_ID CanTxHeader.BitRateSwitch = FDCAN_BRS_OFF; // FDCAN_BRS_OFF / FDCAN_BRS_ON - if (TempUpdate) { // Add temperature update to FDCAN message, 4 bytes - float * fp = (float *)can_tx_buffer; // Point to FDCAN TX buffer - *fp = thermalManager.degHotend(0); // Copy temp to can_tx_buffer + if (tempUpdate) { // Add temperature update to FDCAN message, 4 bytes + float * fp = (float *)can_tx_buffer; // Point to FDCAN TX buffer + *fp = thermalManager.degHotend(0); // Copy temp to can_tx_buffer CanTxHeader.DataLength = FDCAN_DLC_BYTES_4; // Hotend temp in payload only } else @@ -498,7 +500,7 @@ void CAN_toolhead_send_update(bool TempUpdate) { // Called from temperature ISR! CAN_toolhead_error_code |= CAN_ERROR_TOOLHEAD_TX_FIFO_OVERFLOW; } - CanTxHeader.Identifier = CAN_get_virtual_IO(); + CanTxHeader.Identifier = CAN_get_virtual_IO(tempUpdate); if (CAN_request_time_sync) { @@ -553,7 +555,7 @@ void CAN_send_next_string_part() { uint32_t c = MIN(8, len); CanTxHeader.DataLength = (c << CAN_DATALENGTH_OFFSET); // Max message length is 8 bytes (CAN), offset is 16 bits into the DataLength variable - CanTxHeader.Identifier = CAN_get_virtual_IO() | (1 << CAN_ID_STRING_MESSAGE_BIT_POS); + CanTxHeader.Identifier = CAN_get_virtual_IO(false) | CAN_ID_STRING_MESSAGE_BIT_MASK; // Low priority message, wait until TX FIFO is completely empty before sending the message if (HAL_FDCAN_GetTxFifoFreeLevel(&hCAN1) == CAN_FIFO_DEPTH) { From 28ed569a6877c95acc15bdcfc8bd6927c97f3319 Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Sat, 25 Jan 2025 00:16:06 +0800 Subject: [PATCH 19/21] Auto initialize CAN device, auto calculate sample timing Auto initialize CAN device based on CAN_RD_PIN and CAN_TD_PIN Auto calculate bit sample timing based on provided CAN_BAUDRATE and system clock rate. Supported is 1000000, 500000 and 250000 baud. --- Marlin/src/HAL/STM32/CAN_host.cpp | 151 +++++++++++++++----- Marlin/src/HAL/STM32/FDCAN_host.cpp | 149 ++++++++++++------- Marlin/src/HAL/STM32/FDCAN_toolhead.cpp | 115 +++++++++++---- Marlin/src/HAL/STM32/Servo.cpp | 2 +- Marlin/src/HAL/shared/{CAN_host.h => CAN.h} | 46 +++--- Marlin/src/MarlinCore.cpp | 5 +- Marlin/src/feature/runout.h | 2 +- Marlin/src/gcode/gcode.cpp | 2 +- Marlin/src/gcode/host/M115.cpp | 8 +- Marlin/src/gcode/temp/M306.cpp | 2 +- Marlin/src/module/endstops.cpp | 2 +- Marlin/src/module/probe.h | 2 +- Marlin/src/module/temperature.cpp | 2 +- 13 files changed, 331 insertions(+), 157 deletions(-) rename Marlin/src/HAL/shared/{CAN_host.h => CAN.h} (75%) diff --git a/Marlin/src/HAL/STM32/CAN_host.cpp b/Marlin/src/HAL/STM32/CAN_host.cpp index 3af090be1089..3987db7243dd 100644 --- a/Marlin/src/HAL/STM32/CAN_host.cpp +++ b/Marlin/src/HAL/STM32/CAN_host.cpp @@ -45,7 +45,7 @@ #include "../../feature/controllerfan.h" // For controllerFan settings #include "../../libs/numtostr.h" // For float to string conversion -#include "../shared/CAN_host.h" +#include "../shared/CAN.h" // Interrupt handlers controlled by the CAN_IER register extern "C" void CAN1_RX0_IRQHandler(void); // CAN1 FIFO0 interrupt handler (new message, full, overrun) @@ -61,7 +61,14 @@ extern "C" void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan); // C extern "C" void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan); // CAN error interrupt callback #ifndef CAN_BAUDRATE - #define CAN_BAUDRATE 1000000 + #define CAN_BAUDRATE 1000000LU +#endif + +#ifndef CAN_RD_PIN + #define CAN_RD_PIN PB8 +#endif +#ifndef CAN_TD_PIN + #define CAN_TD_PIN PB9 #endif #if (CAN_BAUDRATE != 1000000) && (CAN_BAUDRATE != 500000) && (CAN_BAUDRATE != 250000) && (CAN_BAUDRATE != 125000) @@ -576,69 +583,146 @@ void CAN_host_send_position() { // Send the X, Y, Z and E position to the TOOLHE CAN_host_send_gcode_2params('G', 92, 'Z', current_position.z, 'E', current_position.e); // M92 E Z } -// TODO: SETUP HARDWARE BASED ON CAN_RX, CAN_TX PINS +// Enable a GPIO clock based on the GPIOx address for STM32F4 +void gpio_clock_enable(GPIO_TypeDef *regs) +{ + uint32_t pos = ((uint32_t)regs - GPIOA_BASE) >> 10; + RCC->AHB1ENR |= (1 << pos); + RCC->AHB1ENR; +} + void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle) { // Called by HAL_CAN_Init - if (canHandle->Instance == CAN1) { - if (__HAL_RCC_CAN1_IS_CLK_DISABLED()) - __HAL_RCC_CAN1_CLK_ENABLE(); // Enable CAN1 clock - - if (__HAL_RCC_GPIOB_IS_CLK_DISABLED()) - __HAL_RCC_GPIOB_CLK_ENABLE(); // Enable GPIO B clock - // CAN1 GPIO Configuration - // PB8 ------> CAN1_RX - // PB9 ------> CAN1_TX - - GPIO_InitTypeDef GPIO_InitStruct = { 0 }; - GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9; // Pin PB8 and Pin PB9 - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; - GPIO_InitStruct.Alternate = GPIO_AF9_CAN1; // Alternate function 9 - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // Port B - - // Enable the CAN interrupt handlers - HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 1); - HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn); // Enable CAN1 FIFO0 interrupt handler - - HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 1, 1); // Set CAN interrupt priority - HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn); // Enable CAN1 FIFO1 interrupt handler - } + if (canHandle->Instance == CAN1) + __HAL_RCC_CAN1_CLK_ENABLE(); // Enable CAN1 clock + + if (canHandle->Instance == CAN2) + __HAL_RCC_CAN2_CLK_ENABLE(); // Enable CAN2 clock + + // Use some macros to find the required setup info based on the provided CAN pins + uint32_t _CAN_RD_pin = digitalPinToPinName(CAN_RD_PIN); + uint32_t _CAN_TD_pin = digitalPinToPinName(CAN_TD_PIN); + uint32_t _CAN_RD_function = pinmap_find_function(digitalPinToPinName(CAN_RD_PIN), PinMap_CAN_RD); + uint32_t _CAN_TD_function = pinmap_find_function(digitalPinToPinName(CAN_TD_PIN), PinMap_CAN_TD); + + // Enable the GPIOx device related to the CAN_RD_pin + gpio_clock_enable(get_GPIO_Port(STM_PORT(_CAN_RD_pin))); + + // CAN1 GPIO Configuration + // PB8 ------> CAN1_RX + // PB9 ------> CAN1_TX + + GPIO_InitTypeDef GPIO_InitStruct = { 0 }; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; + GPIO_InitStruct.Pull = GPIO_NOPULL; + + //GPIO_InitStruct.Pin = GPIO_PIN_8; // Pin PB8 and Pin PB9 + GPIO_InitStruct.Pin = STM_GPIO_PIN(STM_PIN(_CAN_RD_pin)); + //GPIO_InitStruct.Alternate = GPIO_AF9_CAN1; + GPIO_InitStruct.Alternate = STM_PIN_AFNUM(_CAN_RD_function); + //HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); + HAL_GPIO_Init(get_GPIO_Port(STM_PORT(_CAN_RD_pin)), &GPIO_InitStruct); + + // Split pin initialisation, perhaps not needed + gpio_clock_enable(get_GPIO_Port(STM_PORT(_CAN_TD_pin))); + //GPIO_InitStruct.Pin = GPIO_PIN_9; // Pin PB8 and Pin PB9 + GPIO_InitStruct.Pin = STM_GPIO_PIN(STM_PIN(_CAN_TD_pin));; + //GPIO_InitStruct.Alternate = GPIO_AF9_CAN1; + GPIO_InitStruct.Alternate = STM_PIN_AFNUM(_CAN_TD_function); + //HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); + HAL_GPIO_Init(get_GPIO_Port(STM_PORT(_CAN_TD_pin)), &GPIO_InitStruct); + + // Enable the CAN interrupt handlers + HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 1); + HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn); // Enable CAN1 FIFO0 interrupt handler + + HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 1, 1); // Set CAN interrupt priority + HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn); // Enable CAN1 FIFO1 interrupt handler } HAL_StatusTypeDef CAN_host_stop() { return HAL_CAN_Stop(&hCAN1); } +int seg1_encode(uint32_t s) { // Ds must be between 1 and 16 +// Timing is encoded in 4 bits with an offset of 1 +// 4 bits: CAN_BTR_TS1_3 : CAN_BTR_TS1_2 : CAN_BTR_TS1_1 : CAN_BTR_TS1_0 + return (--s << CAN_BTR_TS1_Pos); +} + +int seg2_encode(uint32_t s) { // Must be between 1 and 8 +// Timing is encoded in 3 bits with an offset of 1 +// 3 bits: CAN_BTR_TS2_2 : CAN_BTR_TS1_1 : CAN_BTR_TS1_0 + return (--s << CAN_BTR_TS2_Pos); +} + +// Calculate the CAN sample timing, seg1 and seg2, sjw = 1 (no baudrate switching) +// seg1 range: 1-16, seg2 range: 1-8, sjw = 1, so minimum is 3, use a max of 19 clocks per bit +// Will give solutions for all "reasonable" clock rates +int CAN_calculate_segments(uint32_t *seg1, uint32_t *seg2, uint32_t *prescaler) { + + uint32_t CAN_clock = HAL_RCC_GetPCLK1Freq(); // APB1 clock (=PCLK1 clock) + float clocks_per_bit; + *prescaler = 1; + + do { // Check seg1 and seg2 range + do { // Check prescaler + clocks_per_bit = (float)CAN_clock / (*prescaler * CAN_BAUDRATE); // Clocks per bit must be a whole number + } while (((clocks_per_bit != (uint32_t)clocks_per_bit) || (clocks_per_bit > 19)) && (clocks_per_bit > 3) && (*prescaler)++); + + if (clocks_per_bit < 4) // Minimal 4 clocks per bit (SJW + SEG1 + SEG2 = 1 + 1 + 1) + return -1; // Baudrate is not possible + + *seg2 = (clocks_per_bit / 8) + 0.5; // Preferred sample point at 87.5% (7/8) + *seg1 = (int)clocks_per_bit - *seg2 - 1; // sjw = 1; + + } while ((*seg1 + *seg2 + 1 > 19) && (*prescaler)++); // MAX 16 + 2 + 1 clocks + + *seg1 = (*seg1 - 1) << CAN_BTR_TS1_Pos; // Convert to register values + *seg2 = (*seg2 - 1) << CAN_BTR_TS2_Pos; // Convert to register values + + return HAL_OK; +} + HAL_StatusTypeDef CAN_host_start() { HAL_StatusTypeDef status = HAL_OK; + // The CAN clock must be set first because the sample timing depends on it + __HAL_RCC_CAN1_CLK_ENABLE(); + // Initialize TxHeader with constant values TxHeader.ExtId = 0; TxHeader.StdId = 0; TxHeader.RTR = CAN_RTR_DATA; // Data transmission type: CAN_RTR_DATA / CAN_RTR_REMOTE TxHeader.TransmitGlobalTime = DISABLE; // Put timestamp in Data[6-7], requires Time Triggered Communication Mode +uint32_t seg1, seg2, prescaler; +if (CAN_calculate_segments(&seg1, &seg2, &prescaler) != HAL_OK) { + SERIAL_ECHOLNPGM("Impossible CAN baudrate, check CAN clock and baudrate"); + return HAL_ERROR; +} + // CAN peripheral clock is 42MHz (168Mhz / 4) // CAN baud rate = clock frequency / clock divider / prescaler / (1 + TSG1 + TSG2) // Baud rate = 42M / 3 / 1 / (1 + 11 + 2) = 1M baud (Sample point = 12/14=86%) // Baud rate = 42M / 3 / 2 / (1 + 11 + 2) = 500k baud // Baud rate = 42M / 3 / 4 / (1 + 11 + 2) = 250k baud hCAN1.Instance = CAN1; - hCAN1.Init.Prescaler = 3; // 1-1024, 42MHz peripheral clock / 3 --> 14MHz -> 1M baud. 6 --> 500K baud. 12 --> 250K baud. + hCAN1.Init.Prescaler = prescaler; // 1-1024, 42MHz peripheral clock / 3 --> 14MHz -> 1M baud. 6 --> 500K baud. 12 --> 250K baud. hCAN1.Init.AutoBusOff = DISABLE; // DISABLE: Software controlled Bus-off. ENABLE: Automatic hardware controlled (no send/receive) hCAN1.Init.AutoWakeUp = ENABLE; // ENABLE: Automatic hardware controlled bus wakeup. DISABLE: Software controlled bus wakeup. hCAN1.Init.AutoRetransmission = ENABLE; // DISABLE / ENABLE, resend if transmission failed, but locks up if communication fails/cable not connected!!!!!!!!!!!!!!!!! hCAN1.Init.SyncJumpWidth = CAN_SJW_1TQ; // CAN_SJW_1TQ (1-4) Should be 1 - hCAN1.Init.TimeSeg1 = CAN_BS1_11TQ; // CAN_BS1_11TQ (1-16) - hCAN1.Init.TimeSeg2 = CAN_BS2_2TQ; // CAN_BS2_2TQ (1-8) + hCAN1.Init.TimeSeg1 = seg1; // CAN_BS1_11TQ (1-16) + hCAN1.Init.TimeSeg2 = seg2; // CAN_BS2_2TQ (1-8) hCAN1.Init.Mode = CAN_MODE_NORMAL; // CAN_MODE_NORMAL / CAN_MODE_SILENT / CAN_MODE_LOOPBACK / CAN_MODE_SILENT_LOOPBACK hCAN1.Init.TimeTriggeredMode = DISABLE; // TTCAN is used to assign timeslot to the devices for real time applications hCAN1.Init.ReceiveFifoLocked = DISABLE; // Handle RX FIFO overruns. DISABLE: Overwrite previous message with new one. ENABLE: Discard the new message. hCAN1.Init.TransmitFifoPriority = ENABLE; // Handle TX FIFO send order. ENABLE: Chronologically. DISABLE: Transmit lower ID number first. - status = HAL_CAN_Init(&hCAN1); // Calls HAL_CAN_MspInit + status = HAL_CAN_Init(&hCAN1); // Calls HAL_CAN_Init if (status != HAL_OK) return status; CAN_FilterTypeDef sFilterConfig; @@ -695,9 +779,6 @@ HAL_StatusTypeDef CAN_host_start() { pinMode(CAN_LED_PIN, OUTPUT); #endif - #ifdef FDCAN_LED_PIN - pinMode(FDCAN_LED_PIN, OUTPUT); - #endif status = CAN_host_send_gcode_2params('M', 997, 0, 0, 0, 0); // M997, reset toolhead at host startup return status; diff --git a/Marlin/src/HAL/STM32/FDCAN_host.cpp b/Marlin/src/HAL/STM32/FDCAN_host.cpp index e3eef93818aa..b962bd120421 100644 --- a/Marlin/src/HAL/STM32/FDCAN_host.cpp +++ b/Marlin/src/HAL/STM32/FDCAN_host.cpp @@ -41,7 +41,7 @@ #include "../../feature/controllerfan.h" // For controllerFan settings #include "../../libs/numtostr.h" // For float to string conversion -#include "../shared/CAN_host.h" +#include "../shared/CAN.h" // Interrupt handlers extern "C" void FDCAN1_IT0_IRQHandler(void); @@ -49,7 +49,14 @@ extern "C" void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t extern "C" void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs); #ifndef CAN_BAUDRATE - #define CAN_BAUDRATE 1000000 + #define CAN_BAUDRATE 1000000LU +#endif + +#ifndef CAN_RD_PIN + #define CAN_RD_PIN PB8 +#endif +#ifndef CAN_TD_PIN + #define CAN_TD_PIN PB9 #endif #if (CAN_BAUDRATE != 1000000) && (CAN_BAUDRATE != 500000) && (CAN_BAUDRATE != 250000) && (CAN_BAUDRATE != 125000) @@ -564,37 +571,65 @@ void CAN_host_send_position() { // Send the X, Y, Z and E position to the TOOLHE CAN_host_send_gcode_2params('G', 92, 'Z', current_position.z, 'E', current_position.e); // M92 E Z } -// TODO: SETUP HARDWARE BASED ON CAN_RX, CAN_TX PINS +// Enable a GPIO clock based on the GPIOx address for STM32H7 +void gpio_clock_enable(GPIO_TypeDef *regs) +{ + uint32_t pos = ((uint32_t)regs - D3_AHB1PERIPH_BASE) >> 10; + RCC->AHB4ENR |= (1 << pos); + RCC->AHB4ENR; +} + +// TODO: SETUP HARDWARE BASED ON CAN_RD_PIN, CAN_TD_PIN PINS void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* canHandle) { // Called by HAL_FDCAN_Init - if (canHandle->Instance == FDCAN1) { + __HAL_RCC_FDCAN_CLK_ENABLE(); // Enable FDCAN1/2 clock + + // Use some macros to find the required setup info based on the provided CAN pins + uint32_t _CAN_RD_pin = digitalPinToPinName(CAN_RD_PIN); + uint32_t _CAN_TD_pin = digitalPinToPinName(CAN_TD_PIN); + uint32_t _CAN_RD_function = pinmap_find_function(digitalPinToPinName(CAN_RD_PIN), PinMap_CAN_RD); + uint32_t _CAN_TD_function = pinmap_find_function(digitalPinToPinName(CAN_TD_PIN), PinMap_CAN_TD); + + // Enable the GPIOx device related to the CAN_RD_pin + gpio_clock_enable(get_GPIO_Port(STM_PORT(_CAN_RD_pin))); + + GPIO_InitTypeDef GPIO_InitStruct = { 0 }; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; + GPIO_InitStruct.Pull = STM_PIN_PUPD(_CAN_RD_function); + + // SETUP CAN_RD_PIN + GPIO_InitStruct.Pin = STM_GPIO_PIN(STM_PIN(_CAN_RD_pin)); + GPIO_InitStruct.Alternate = STM_PIN_AFNUM(_CAN_RD_function); + HAL_GPIO_Init(get_GPIO_Port(STM_PORT(_CAN_RD_pin)), &GPIO_InitStruct); + + // SETUP CAN_TD_PIN (Separated for flexibility, might not be needed) + // Enable the GPIOx device related to the CAN_TD_pin + gpio_clock_enable(get_GPIO_Port(STM_PORT(_CAN_TD_pin))); + GPIO_InitStruct.Pin = STM_GPIO_PIN(STM_PIN(_CAN_TD_pin)); + GPIO_InitStruct.Alternate = STM_PIN_AFNUM(_CAN_TD_function); + HAL_GPIO_Init(get_GPIO_Port(STM_PORT(_CAN_TD_pin)), &GPIO_InitStruct); + + // Enabled the FDCAN interrupt handler for FDCAN1 or FDCAN2 + HAL_NVIC_SetPriority((canHandle->Instance == FDCAN1) ? FDCAN1_IT0_IRQn : FDCAN2_IT0_IRQn, 1, 1); // Set FDCAN interrupt priority + HAL_NVIC_EnableIRQ( (canHandle->Instance == FDCAN1) ? FDCAN1_IT0_IRQn : FDCAN2_IT0_IRQn); // Enable FDCAN interrupt handler line 0 (default) +} -// Select the FDCAN clock source - __HAL_RCC_FDCAN_CONFIG(RCC_FDCANCLKSOURCE_HSE); // 25MHz, select external clock oscillator -// __HAL_RCC_FDCAN_CONFIG(RCC_FDCANCLKSOURCE_PLL); // 55MHz, select PLL -// __HAL_RCC_FDCAN_CONFIG(RCC_FDCANCLKSOURCE_PLL2); // 80MHz, select PLL2 +// Calculate the CAN sample timing, seg1 and seg2, sjw = 1 (no baudrate switching) +// seg1 range: 2-256, seg2 range: 1-128, SJW = 1, so minimum is 4 clocks per bit +int FDCAN_calculate_segments(uint32_t *seg1, uint32_t *seg2) { - if (__HAL_RCC_FDCAN_IS_CLK_DISABLED()) - __HAL_RCC_FDCAN_CLK_ENABLE(); // Enable FDCAN clock - - if (__HAL_RCC_GPIOB_IS_CLK_DISABLED()) // Should be enabled by Marlin already - __HAL_RCC_GPIOB_CLK_ENABLE(); // Enable GPIO B clock - // CAN1 GPIO Configuration - // PB8 ------> CAN1_RX - // PB9 ------> CAN1_TX - - GPIO_InitTypeDef GPIO_InitStruct = {0}; - GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9; // Pin PB8 and Pin PB9 - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; - GPIO_InitStruct.Alternate = GPIO_AF9_FDCAN1; // Alternate function 9 - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // Port B - - // Enabled the FDCAN interrupt handler - HAL_NVIC_SetPriority(FDCAN1_IT0_IRQn, 1, 1); // Set FDCAN interrupt priority - HAL_NVIC_EnableIRQ(FDCAN1_IT0_IRQn); // Enable FDCAN interrupt handler line 0 (default) - } + uint32_t CAN_clock = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_FDCAN); + + float clocks_per_bit = CAN_clock / CAN_BAUDRATE; // Clocks per bit must be a whole number + + if ((clocks_per_bit != int(clocks_per_bit)) || (clocks_per_bit < 4)) // Minimal 4 clocks per bit (1+2+1) + return -1; // Baudrate is not possible + + *seg2 = (clocks_per_bit / 8) + 0.5; // Preferred sample point at 87.5% (7/8) + *seg1 = uint32_t(clocks_per_bit) - *seg2 - 1; // SJW = 1; + + return HAL_OK; } HAL_StatusTypeDef CAN_host_stop() { @@ -605,22 +640,27 @@ HAL_StatusTypeDef CAN_host_start() { HAL_StatusTypeDef status = HAL_OK; +// The FDCAN clock source must to be set early because the sample timing depends on it + __HAL_RCC_FDCAN_CONFIG(RCC_FDCANCLKSOURCE_HSE); // 25MHz, select external crystal clock oscillator +// __HAL_RCC_FDCAN_CONFIG(RCC_FDCANCLKSOURCE_PLL); // 55MHz, select PLL +// __HAL_RCC_FDCAN_CONFIG(RCC_FDCANCLKSOURCE_PLL2); // 80MHz, select PLL2 + // Initialize TxHeader with constant values - TxHeader.FDFormat = FDCAN_CLASSIC_CAN; // FDCAN_CLASSIC_CAN / FDCAN_FD_CAN - TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; // FDCAN_NO_TX_EVENTS / FDCAN_STORE_TX_EVENTS - TxHeader.MessageMarker = 0; // 0-0xFF for tracking messages in FIFO buffer - TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE; // FDCAN_ESI_PASSIVE / FDCAN_ESI_ACTIVE (ACTIVE will notify transmission errors) - TxHeader.TxFrameType = FDCAN_DATA_FRAME; // FDCAN_DATA_FRAME / FDCAN_REMOTE_FRAME - TxHeader.IdType = FDCAN_STANDARD_ID; // FDCAN_STANDARD_ID / FDCAN_EXTENDED_ID - TxHeader.BitRateSwitch = FDCAN_BRS_OFF; // FDCAN_BRS_OFF / FDCAN_BRS_ON - TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; // FDCAN_NO_TX_EVENTS / FDCAN_STORE_TX_EVENTS (Do not store TX events) - - hCAN1.Instance = FDCAN1; // The FDCAN device to use - hCAN1.Init.FrameFormat = FDCAN_FRAME_CLASSIC; // FDCAN_FRAME_CLASSIC / FDCAN_FRAME_FD_BRS / FDCAN_FRAME_FD_NO_BRS (Bit Rate Switching) - hCAN1.Init.Mode = FDCAN_MODE_NORMAL; // FDCAN_MODE_NORMAL / FDCAN_MODE_EXTERNAL_LOOPBACK / FDCAN_MODE_INTERNAL_LOOPBACK / FDCAN_MODE_BUS_MONITORING / FDCAN_MODE_RESTRICTED_OPERATION - hCAN1.Init.AutoRetransmission = DISABLE; // Auto Retransmission of message if error occured, warning: can cause lockup - hCAN1.Init.TransmitPause = DISABLE; // Transmit Pause to allow for other device to send message - hCAN1.Init.ProtocolException = DISABLE; // ProtocolException handling + TxHeader.FDFormat = FDCAN_CLASSIC_CAN; // FDCAN_CLASSIC_CAN / FDCAN_FD_CAN + TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; // FDCAN_NO_TX_EVENTS / FDCAN_STORE_TX_EVENTS + TxHeader.MessageMarker = 0; // 0-0xFF for tracking messages in FIFO buffer + TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE; // FDCAN_ESI_PASSIVE / FDCAN_ESI_ACTIVE (ACTIVE will notify transmission errors) + TxHeader.TxFrameType = FDCAN_DATA_FRAME; // FDCAN_DATA_FRAME / FDCAN_REMOTE_FRAME + TxHeader.IdType = FDCAN_STANDARD_ID; // FDCAN_STANDARD_ID / FDCAN_EXTENDED_ID + TxHeader.BitRateSwitch = FDCAN_BRS_OFF; // FDCAN_BRS_OFF / FDCAN_BRS_ON + TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; // FDCAN_NO_TX_EVENTS / FDCAN_STORE_TX_EVENTS (Do not store TX events) + + hCAN1.Instance = FDCAN1; // The FDCAN device to use + hCAN1.Init.FrameFormat = FDCAN_FRAME_CLASSIC; // FDCAN_FRAME_CLASSIC / FDCAN_FRAME_FD_BRS / FDCAN_FRAME_FD_NO_BRS (Bit Rate Switching) + hCAN1.Init.Mode = FDCAN_MODE_NORMAL; // FDCAN_MODE_NORMAL / FDCAN_MODE_EXTERNAL_LOOPBACK / FDCAN_MODE_INTERNAL_LOOPBACK / FDCAN_MODE_BUS_MONITORING / FDCAN_MODE_RESTRICTED_OPERATION + hCAN1.Init.AutoRetransmission = DISABLE; // Auto Retransmission of message if error occured, warning: can cause lockup + hCAN1.Init.TransmitPause = DISABLE; // Transmit Pause to allow for other device to send message + hCAN1.Init.ProtocolException = DISABLE; // ProtocolException handling // FDCAN baud rate = FDCAN Clock / Prescaler / (SJW + TSG1 + TSG2) // Sample-Point from 50 to 90% (87.5 % is the preferred value used by CANopen and DeviceNet, 75% is the ARINC 825 default) @@ -633,10 +673,16 @@ HAL_StatusTypeDef CAN_host_start() { // PLL2 80MHz: ( 2 1 69 10) ( 4 1 34 5) ( 8 1 16 3) --> 500K baud // PLL2 80MHz: ( 4 1 69 10) ( 8 1 34 5) (16 1 16 3) --> 250K baud - hCAN1.Init.NominalPrescaler = 1; // Arbitration/data clock prescaler/divider (1-512) - hCAN1.Init.NominalSyncJumpWidth = 1; // Arbitration/data Sync Jump Width (1-128 SJW), should be 1 - hCAN1.Init.NominalTimeSeg1 = 21; // Arbitration/data period 1 (2-256) - hCAN1.Init.NominalTimeSeg2 = 3; // Arbitration/data period 2 (2-128) + uint32_t seg1, seg2; + if (FDCAN_calculate_segments(&seg1, &seg2) != HAL_OK) { + SERIAL_ECHOLNPGM("Impossible CAN baudrate, check CAN clock and baudrate"); + return HAL_ERROR; + } + + hCAN1.Init.NominalPrescaler = 1; // Arbitration/data clock prescaler/divider (1-512) + hCAN1.Init.NominalSyncJumpWidth = 1; // Arbitration/data Sync Jump Width (1-128 SJW), should be 1 + hCAN1.Init.NominalTimeSeg1 = seg1; // Arbitration/data period 1 (2-256) // 21 + hCAN1.Init.NominalTimeSeg2 = seg2; // Arbitration/data period 2 (2-128) // 3 /* No bitrate switching, not used: hCAN1.Init.DataPrescaler = 1; // Arbitration/data clock prescaler/divider (1-32) @@ -732,14 +778,15 @@ HAL_StatusTypeDef CAN_host_start() { SERIAL_ECHOLNPGM("FDCAN BAUDRATE: ", baudrate); if (baudrate != CAN_BAUDRATE) { - SERIAL_ECHOLNPGM("Error: Incorrect FDCAN baudrate generated: ", baudrate, " FDCAN_BAUDRATE=", CAN_BAUDRATE); + SERIAL_ECHOLNPGM(">>> Host ", CAN_ERROR_MSG_INVALID_BAUDRATE, ": ", baudrate, " CAN_BAUDRATE=", CAN_BAUDRATE); CAN_host_error_code |= CAN_ERROR_HOST_INVALID_BAUDRATE; } #endif - #ifdef FDCAN_LED_PIN - pinMode(FDCAN_LED_PIN, OUTPUT); + #ifdef CAN_LED_PIN + pinMode(CAN_LED_PIN, OUTPUT); #endif + status = CAN_host_send_gcode_2params('M', 997, 0, 0, 0, 0); // M997, reset toolhead at host startup return status; diff --git a/Marlin/src/HAL/STM32/FDCAN_toolhead.cpp b/Marlin/src/HAL/STM32/FDCAN_toolhead.cpp index 66d4778a768c..e1807f5a7915 100644 --- a/Marlin/src/HAL/STM32/FDCAN_toolhead.cpp +++ b/Marlin/src/HAL/STM32/FDCAN_toolhead.cpp @@ -45,7 +45,7 @@ #include "../../feature/controllerfan.h" #include "../../core/serial.h" -#include "../shared/CAN_HOST.h" +#include "../shared/CAN.h" extern "C" void TIM16_IRQHandler(void); // Override weak functions extern "C" void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs); @@ -58,7 +58,14 @@ extern "C" void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t #define CAN_DATALENGTH_OFFSET 16 // Used for CAN TxHeader DataLength record #ifndef CAN_BAUDRATE - #define CAN_BAUDRATE 1000000 + #define CAN_BAUDRATE 1000000LU +#endif + +#ifndef CAN_RD_PIN + #define CAN_RD_PIN PB0 +#endif +#ifndef CAN_TD_PIN + #define CAN_TD_PIN PB1 #endif #define FDCAN_RX_FIFO0_MASK (FDCAN_IR_RF0L | FDCAN_IR_RF0F | FDCAN_IR_RF0N) // FIFO0: Message lost | FIFO full | New message @@ -226,8 +233,8 @@ void process_can_queue() { #endif } else - if (!(identifier & CAN_EXTENDED_ID_MARKER_MASK)) // Standard ID message, so parameters were added to the Gcode - CAN_gcode_buffer.trunc(backupLength); // Cut off the part of the Gcode that was added, so we can process the CAN message again + if (!(identifier & CAN_EXTENDED_ID_MARKER_MASK)) // Standard ID message, so parameters were added to the Gcode + CAN_gcode_buffer.trunc(backupLength); // Cut off the part of the Gcode that was added, so we can process the CAN message again } else CAN_queue_tail = (CAN_queue_tail + 1) % CAN_QUEUE_DEPTH; // Always advance tail @@ -319,35 +326,49 @@ void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs) } +// Enable a GPIO clock based on the GPIOx address for STM32G0 +void gpio_clock_enable(GPIO_TypeDef *regs) +{ + // Or use: STM_PORT(CAN_TD_pin) GPIOA=0, GPIOB=1 etc. + uint32_t pos = ((uint32_t)regs - IOPORT_BASE) >> 10; + RCC->IOPENR |= (1 << pos); + RCC->IOPENR; +} + void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* fdcanHandle) { // Called automatically by FDCAN_init(), configure GPIO for CAN, enable interrupts - RCC_PeriphCLKInitTypeDef PeriphClkInit = { 0 }; + // STM32G0B1 and STM32G0 C1 only - PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_FDCAN; -//PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_HSE; // 8MHz external crystal -//PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL; // 64MHz - PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_PCLK1; // 64MHz - - HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit); + // FDCAN2 GPIO Configuration + // PB0 AF3 ------> FDCAN2_RD + // PB1 AF3 ------> FDCAN2_TD - // STM32G0B1 and STM32G0 C1 only // CAN1/2 clock enable (one clock for both CAN devices) - if (__HAL_RCC_FDCAN_IS_CLK_DISABLED()) - __HAL_RCC_FDCAN_CLK_ENABLE(); + __HAL_RCC_FDCAN_CLK_ENABLE(); - if (__HAL_RCC_GPIOB_IS_CLK_DISABLED()) - __HAL_RCC_GPIOB_CLK_ENABLE(); // ENABLE GPIO B CLOCK - // FDCAN2 GPIO Configuration - // PB0 AF3 ------> FDCAN2_RX - // PB1 AF3 ------> FDCAN2_TX + // Use some macros to find the required setup info based on the provided CAN pins + uint32_t _CAN_RD_pin = digitalPinToPinName(CAN_RD_PIN); + uint32_t _CAN_TD_pin = digitalPinToPinName(CAN_TD_PIN); + uint32_t _CAN_RD_function = pinmap_find_function(digitalPinToPinName(CAN_RD_PIN), PinMap_CAN_RD); + uint32_t _CAN_TD_function = pinmap_find_function(digitalPinToPinName(CAN_TD_PIN), PinMap_CAN_TD); + + // Enable the GPIOx device related to the CAN_RD_pin + gpio_clock_enable(get_GPIO_Port(STM_PORT(_CAN_RD_pin))); // PB0 GPIO_InitTypeDef GPIO_InitStruct = { 0 }; - GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1; // Pin PB0 and Pin PB1 - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // Alternate function - GPIO_InitStruct.Pull = GPIO_NOPULL; // No pullup or pulldown - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // High frequency device - GPIO_InitStruct.Alternate = GPIO_AF3_FDCAN2; // Alternate function 3 - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // Port B + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // High frequency device + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // Alternate function + GPIO_InitStruct.Pin = STM_GPIO_PIN(STM_PIN(_CAN_RD_pin)) | STM_GPIO_PIN(STM_PIN(_CAN_TD_pin)); + GPIO_InitStruct.Pull = STM_PIN_PUPD(_CAN_RD_function); + GPIO_InitStruct.Alternate = STM_PIN_AFNUM(_CAN_RD_function); + HAL_GPIO_Init(get_GPIO_Port(STM_PORT(_CAN_RD_pin)), &GPIO_InitStruct); + + //NOTE: Separating the pin initialisation causes issues, why? + //Enable the GPIOx device related to the CAN_TD_pin + //gpio_clock_enable(get_GPIO_Port(STM_PORT(_CAN_TD_pin))); + //GPIO_InitStruct.Pin = STM_GPIO_PIN(STM_PIN(_CAN_TD_pin)); + //GPIO_InitStruct.Alternate = STM_PIN_AFNUM(_CAN_TD_function); + //HAL_GPIO_Init(get_GPIO_Port(STM_PORT(_CAN_TD_pin)), &GPIO_InitStruct); // Enable CAN interrupts HAL_NVIC_SetPriority(TIM16_FDCAN_IT0_IRQn, 1, 1); // Set interrupt priority @@ -355,7 +376,31 @@ void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* fdcanHandle) { // Called automatical } // HAL_FDCAN_MspInit -HAL_StatusTypeDef CAN_toolhead_start(void) { // Start the CAN device +// Calculate the CAN sample timing, seg1 and seg2, sjw = 1 (no baudrate switching) +// seg1 range: 2-256, seg2 range: 2-128, SJW = 1, so minimum is 5 clocks per bit +int FDCAN_calculate_segments(uint32_t *seg1, uint32_t *seg2) { + + uint32_t CAN_clock = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_FDCAN); + float clocks_per_bit = CAN_clock / CAN_BAUDRATE; // Clocks per bit must be a whole number + + if ((clocks_per_bit != int(clocks_per_bit)) || (clocks_per_bit < 5)) // Minimal 5 clocks per bit (2+2+1) + return -1; // Baudrate is not possible + + *seg2 = (clocks_per_bit / 8) + 0.5; // Preferred sample point at 87.5% (7/8) + *seg1 = uint32_t(clocks_per_bit) - *seg2 - 1; // sjw = 1; + + return HAL_OK; +} + +HAL_StatusTypeDef CAN_toolhead_start() { // Start the CAN device + + // The CAN clock and clock source must to be set first because the sample timing depends on it + RCC_PeriphCLKInitTypeDef PeriphClkInit = { 0 }; + PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_FDCAN; + PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_HSE; // 8MHz external crystal +//PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL; // 64MHz +//PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_PCLK1; // 64MHz + HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit); // Select the FDCAN clock source HAL_StatusTypeDef status = HAL_OK; // CAN baud rate = Device Clock Frequency / Clock Divider / Prescaler / (SJW + TSG1 + TSG2) @@ -364,6 +409,14 @@ HAL_StatusTypeDef CAN_toolhead_start(void) { // Start the CAN device // Baud rate = 64M / 1 / 8 / (1 + 13 + 2) = 500K Bit sample point = (1 + 13) / (1 + 13 + 2) = 87.5% // Baud rate = 64M / 1 / 16 / (1 + 13 + 2) = 250K Bit sample point = (1 + 13) / (1 + 13 + 2) = 87.5% + uint32_t seg1, seg2; + if (FDCAN_calculate_segments(&seg1, &seg2) != HAL_OK) { + SERIAL_ECHOLNPGM("Impossible CAN baudrate, check CAN clock and baudrate"); + return HAL_ERROR; + } + + __HAL_RCC_FDCAN_CLK_DISABLE(); // Disable for startup, sample calculation were done + hCAN1.Instance = FDCAN2; // The FDCAN device used hCAN1.Init.ClockDivider = FDCAN_CLOCK_DIV1; // Clock divider 1 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 hCAN1.Init.FrameFormat = FDCAN_FRAME_CLASSIC; // FDCAN_FRAME_CLASSIC / FDCAN_FRAME_FD_BRS / FDCAN_FRAME_FD_NO_BRS (Bit Rate Switching) @@ -372,10 +425,10 @@ HAL_StatusTypeDef CAN_toolhead_start(void) { // Start the CAN device hCAN1.Init.TransmitPause = DISABLE; // Transmit Pause to allow for other device to send message hCAN1.Init.ProtocolException = DISABLE; // ProtocolException handling - hCAN1.Init.NominalPrescaler = 4; // Arbitration/data clock prescaler (1-512) - hCAN1.Init.NominalSyncJumpWidth = 1; // Arbitration/data sync jump width (1-128) - hCAN1.Init.NominalTimeSeg1 = 13; // Arbitration/data period 1 (2-256) - hCAN1.Init.NominalTimeSeg2 = 2; // Arbitration/data period 2 (2-128) + hCAN1.Init.NominalPrescaler = 1; // Arbitration/data clock prescaler (1-512) + hCAN1.Init.NominalSyncJumpWidth = 1; // Arbitration/data Sync Jump Width (1-128) + hCAN1.Init.NominalTimeSeg1 = seg1; // Arbitration/data period 1 (2-256) + hCAN1.Init.NominalTimeSeg2 = seg2; // Arbitration/data period 2 (2-128) /* Not used, no bitrate switching hCAN1.Init.DataPrescaler = 4; // Arbitration/data clock prescaler (1-32) @@ -457,7 +510,7 @@ HAL_StatusTypeDef CAN_toolhead_start(void) { // Start the CAN device SERIAL_ECHOLNPGM(">>> FDCAN BAUDRATE: ", baudrate); if (baudrate != CAN_BAUDRATE) { - SERIAL_ECHOLNPGM(">>> ", CAN_ERROR_MSG_INVALID_BAUDRATE, ": ", baudrate, " CAN_BAUDRATE=", CAN_BAUDRATE); + SERIAL_ECHOLNPGM(">>> Toolhead ", CAN_ERROR_MSG_INVALID_BAUDRATE, ": ", baudrate, " CAN_BAUDRATE=", CAN_BAUDRATE); CAN_toolhead_error_code |= CAN_ERROR_TOOLHEAD_INVALID_BAUDRATE; } diff --git a/Marlin/src/HAL/STM32/Servo.cpp b/Marlin/src/HAL/STM32/Servo.cpp index 67d9773d7c1d..b7bace17579a 100644 --- a/Marlin/src/HAL/STM32/Servo.cpp +++ b/Marlin/src/HAL/STM32/Servo.cpp @@ -30,7 +30,7 @@ #include "Servo.h" #if ENABLED(CAN_HOST) - #include "../shared/CAN_host.h" + #include "../shared/CAN.h" #endif static uint_fast8_t servoCount = 0; diff --git a/Marlin/src/HAL/shared/CAN_host.h b/Marlin/src/HAL/shared/CAN.h similarity index 75% rename from Marlin/src/HAL/shared/CAN_host.h rename to Marlin/src/HAL/shared/CAN.h index 0b2f9c9e5226..051100972fe2 100644 --- a/Marlin/src/HAL/shared/CAN_host.h +++ b/Marlin/src/HAL/shared/CAN.h @@ -40,25 +40,25 @@ #define CAN_ID_PARAMETER_COUNT_MASK 0b111 // Parameter count #define CAN_ID_GCODE_TYPE_MASK 0b11 // Gcode type mask -#define CAN_ID_PROBE_MASK (1 << 0) // Virtual IO bit for Z-probe pin +#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_MASK (1 << 1) // Virtual IO bit for filament pin +#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_MASK (1 << 2) // Virtual IO bit for X-min pin +#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_MASK (1 << 3) // Virtual IO bit for Y-min pin +#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_MASK (1 << 4) // Virtual IO bit for Z-min pin +#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_MASK (1 << 5) // Signals the toolhead sent a string message +#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_MASK (1 << 6) // Signals the toolhead requests setup information +#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_MASK (1 << 7) // Signals the toolhead signals a TMC Over-Temp error +#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_MASK (1 << 8) // Signals the toolhead requested a time sync +#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_MASK (1 << 9) // Signals the toolhead encountered an error +#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 @@ -86,23 +86,21 @@ #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 "Toolhead CAN RX FIFO overflow" -#define CAN_ERROR_MSG_TX_FIFO_OVERFLOW "Toolhead CAN TX FIFO overflow" -#define CAN_ERROR_MSG_INCOMPLETE_GCODE "Toolhead incomplete Gcode message received" -#define CAN_ERROR_MSG_MARLIN_CMM_BUF_OVERFLOW "Toolhead Marlin CMD buffer overflow" -#define CAN_ERROR_MSG_INVALID_BAUDRATE "Toolhead Incorrect CAN baudrate" +#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(); // 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 +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_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(); \ No newline at end of file diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index d7a320fcc436..cef754111980 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -35,7 +35,7 @@ #include "HAL/shared/cpu_exception/exception_hook.h" #if ANY(CAN_HOST, CAN_TOOLHEAD) - #include "HAL/shared/CAN_host.h" + #include "HAL/shared/CAN.h" #endif #if ENABLED(HAS_ADXL345_ACCELEROMETER) @@ -1256,8 +1256,7 @@ void setup() { #endif #if ENABLED(CAN_TOOLHEAD) - SERIAL_ECHOLN(millis(), - F(">>> CAN Start: "), + SERIAL_ECHOLN( F(">>> CAN Start: "), CAN_toolhead_start() == HAL_OK ? F("OK") : F("FAILED!") ); #endif diff --git a/Marlin/src/feature/runout.h b/Marlin/src/feature/runout.h index 5d3637d367af..84c5c740df30 100644 --- a/Marlin/src/feature/runout.h +++ b/Marlin/src/feature/runout.h @@ -34,7 +34,7 @@ #include "../MarlinCore.h" // for printingIsActive() #if ENABLED(CAN_HOST) - #include "../HAL/shared/CAN_host.h" + #include "../HAL/shared/CAN.h" #endif #include "../inc/MarlinConfig.h" diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp index 984b63ea8f11..7448997358a5 100644 --- a/Marlin/src/gcode/gcode.cpp +++ b/Marlin/src/gcode/gcode.cpp @@ -70,7 +70,7 @@ GcodeSuite gcode; #endif #if ENABLED(CAN_HOST) - #include "../HAL/shared/CAN_host.h" + #include "../HAL/shared/CAN.h" #include "../libs/buzzer.h" #endif diff --git a/Marlin/src/gcode/host/M115.cpp b/Marlin/src/gcode/host/M115.cpp index cd2f6c4da805..f5bf4b09466c 100644 --- a/Marlin/src/gcode/host/M115.cpp +++ b/Marlin/src/gcode/host/M115.cpp @@ -35,18 +35,14 @@ #include "../../feature/caselight.h" #endif -#if ENABLED(CAN_TOOLHEAD) - #include "../../HAL/shared/CAN_host.h" +#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 -#if ENABLED(CAN_HOST) - #include "../../HAL/shared/CAN_host.h" -#endif - //#define MINIMAL_CAP_LINES // Don't even mention the disabled capabilities #if ENABLED(EXTENDED_CAPABILITIES_REPORT) diff --git a/Marlin/src/gcode/temp/M306.cpp b/Marlin/src/gcode/temp/M306.cpp index 9889d90d2c0a..01707735f4a9 100644 --- a/Marlin/src/gcode/temp/M306.cpp +++ b/Marlin/src/gcode/temp/M306.cpp @@ -30,7 +30,7 @@ #include "../../libs/numtostr.h" #if ENABLED(CAN_TOOLHEAD) - #include "../../HAL/shared/CAN_host.h" + #include "../../HAL/shared/CAN.h" #endif /** diff --git a/Marlin/src/module/endstops.cpp b/Marlin/src/module/endstops.cpp index db20ffcfa988..6dd50104c6de 100644 --- a/Marlin/src/module/endstops.cpp +++ b/Marlin/src/module/endstops.cpp @@ -66,7 +66,7 @@ #endif #if ENABLED(CAN_TOOLHEAD) - #include "../HAL/shared/CAN_host.h" + #include "../HAL/shared/CAN.h" #endif #define DEBUG_OUT ALL(USE_SENSORLESS, DEBUG_LEVELING_FEATURE) diff --git a/Marlin/src/module/probe.h b/Marlin/src/module/probe.h index 496ea205a463..25466feb2d8b 100644 --- a/Marlin/src/module/probe.h +++ b/Marlin/src/module/probe.h @@ -30,7 +30,7 @@ #include "motion.h" #if ENABLED(CAN_HOST) - #include "../HAL/shared/CAN_host.h" + #include "../HAL/shared/CAN.h" #endif #if ENABLED(BLTOUCH) diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp index f509d0f23989..af4f5f8dd856 100644 --- a/Marlin/src/module/temperature.cpp +++ b/Marlin/src/module/temperature.cpp @@ -51,7 +51,7 @@ #endif #if ENABLED(CAN_HOST) - #include "../HAL/shared/CAN_host.h" + #include "../HAL/shared/CAN.h" #endif #if ENABLED(DWIN_CREALITY_LCD) From 22bca330b9b1cd0f274c7e1eca71eac9201ea675 Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Wed, 29 Jan 2025 23:37:51 +0800 Subject: [PATCH 20/21] Trying to resolve some conflicts with bugfix-2.1.x Trying to resolve some conflicts with bugfix-2.1.x --- Marlin/src/HAL/STM32/inc/SanityCheck.h | 2 +- Marlin/src/feature/runout.h | 2 +- Marlin/src/module/endstops.cpp | 4 ++-- Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Marlin/src/HAL/STM32/inc/SanityCheck.h b/Marlin/src/HAL/STM32/inc/SanityCheck.h index 93bc2aef37b5..5d069116c699 100644 --- a/Marlin/src/HAL/STM32/inc/SanityCheck.h +++ b/Marlin/src/HAL/STM32/inc/SanityCheck.h @@ -36,7 +36,7 @@ #error "SDCARD_EEPROM_EMULATION requires SDSUPPORT. Enable SDSUPPORT or choose another EEPROM emulation." #endif -#if !ANY(STM32F4xx, STM32H7xx) && ENABLED(FLASH_EEPROM_LEVELING) +#if NONE(STM32F4xx, STM32H7xx) && ENABLED(FLASH_EEPROM_LEVELING) #error "FLASH_EEPROM_LEVELING is currently only supported on STM32F4/H7 hardware." #endif diff --git a/Marlin/src/feature/runout.h b/Marlin/src/feature/runout.h index 84c5c740df30..66a5a3c76cf9 100644 --- a/Marlin/src/feature/runout.h +++ b/Marlin/src/feature/runout.h @@ -60,7 +60,7 @@ #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 + #define FILAMENT_IS_OUT(N...) (READ(FIL_RUNOUT##N##_PIN) == FIL_RUNOUT##N##_STATE) #endif typedef Flags< diff --git a/Marlin/src/module/endstops.cpp b/Marlin/src/module/endstops.cpp index 6dd50104c6de..b12c93906951 100644 --- a/Marlin/src/module/endstops.cpp +++ b/Marlin/src/module/endstops.cpp @@ -27,7 +27,7 @@ #include "endstops.h" #include "stepper.h" -#if HAS_STATUS_MESSAGE +#if ANY(HAS_STATUS_MESSAGE, VALIDATE_HOMING_ENDSTOPS) #include "../lcd/marlinui.h" #endif @@ -543,7 +543,7 @@ void __O2 Endstops::report_states() { } #undef _CASE_RUNOUT #elif HAS_FILAMENT_SENSOR - print_es_state(!FILAMENT_IS_OUT()); + print_es_state(!FILAMENT_IS_OUT(), F(STR_FILAMENT)); #endif TERN_(BLTOUCH, bltouch._reset_SW_mode()); diff --git a/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h b/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h index 955b5d46e243..8d12c8bc63c6 100644 --- a/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h +++ b/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h @@ -42,15 +42,15 @@ //#define SRAM_EEPROM_EMULATION // Use BackSRAM-based EEPROM emulation //#define FLASH_EEPROM_EMULATION // Use Flash-based EEPROM emulation #define I2C_EEPROM // Need use jumpers set i2c for EEPROM -#define MARLIN_EEPROM_SIZE 0x1000 // 4K +#define MARLIN_EEPROM_SIZE 0x1000U // 4K #if ENALBED(CAN_HOST) // Onboard I2C EEPROM is disconnected by enabling CAN bus jumpers // NOTE: 2 Patch wires are required to reconnect the I2C EEPROM //#define I2C_SCL_PIN PA8 // BLTouch servo control pin //#define I2C_SDA_PIN PC9 // SPI3_CS TFT Card chip select pin #else - #define I2C_SCL_PIN PB8 // I2C_SCL to CAN_RX - #define I2C_SDA_PIN PB9 // I2C_SDA to CAN_TX + #define I2C_SCL_PIN PB8 // I2C_SCL and CAN_RX + #define I2C_SDA_PIN PB9 // I2C_SDA and CAN_TX #endif // From ddcaeb840a5a5dd2c94670c396b69d2ce7f72a68 Mon Sep 17 00:00:00 2001 From: rondlh <77279634+rondlh@users.noreply.github.com> Date: Thu, 30 Jan 2025 03:21:17 +0800 Subject: [PATCH 21/21] Resolving conflicts, second try --- Marlin/src/HAL/STM32/inc/SanityCheck.h | 2 +- Marlin/src/feature/runout.h | 1 - Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h | 10 ++-------- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/Marlin/src/HAL/STM32/inc/SanityCheck.h b/Marlin/src/HAL/STM32/inc/SanityCheck.h index 5d069116c699..bdcc6ab9f080 100644 --- a/Marlin/src/HAL/STM32/inc/SanityCheck.h +++ b/Marlin/src/HAL/STM32/inc/SanityCheck.h @@ -37,7 +37,7 @@ #endif #if NONE(STM32F4xx, STM32H7xx) && ENABLED(FLASH_EEPROM_LEVELING) - #error "FLASH_EEPROM_LEVELING is currently only supported on STM32F4/H7 hardware." + #error "FLASH_EEPROM_LEVELING is currently only supported on STM32F4/H7 hardware." // IRON #endif #if ENABLED(SERIAL_STATS_MAX_RX_QUEUED) diff --git a/Marlin/src/feature/runout.h b/Marlin/src/feature/runout.h index 66a5a3c76cf9..1fa351f42135 100644 --- a/Marlin/src/feature/runout.h +++ b/Marlin/src/feature/runout.h @@ -59,7 +59,6 @@ #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 FILAMENT_IS_OUT(N...) (READ(FIL_RUNOUT##N##_PIN) == FIL_RUNOUT##N##_STATE) #endif diff --git a/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h b/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h index 8d12c8bc63c6..3e403a340cce 100644 --- a/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h +++ b/Marlin/src/pins/stm32f4/pins_MKS_MONSTER8_common.h @@ -44,14 +44,8 @@ #define I2C_EEPROM // Need use jumpers set i2c for EEPROM #define MARLIN_EEPROM_SIZE 0x1000U // 4K -#if ENALBED(CAN_HOST) // Onboard I2C EEPROM is disconnected by enabling CAN bus jumpers -// NOTE: 2 Patch wires are required to reconnect the I2C EEPROM -//#define I2C_SCL_PIN PA8 // BLTouch servo control pin -//#define I2C_SDA_PIN PC9 // SPI3_CS TFT Card chip select pin -#else - #define I2C_SCL_PIN PB8 // I2C_SCL and CAN_RX - #define I2C_SDA_PIN PB9 // I2C_SDA and CAN_TX -#endif +#define I2C_SCL_PIN PB8 // I2C_SCL and CAN_RX +#define I2C_SDA_PIN PB9 // I2C_SDA and CAN_TX // // Servos