My Marlin configs for Fabrikator Mini and CTC i3 Pro B
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128
  1. /**
  2. * Marlin 3D Printer Firmware
  3. * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
  4. *
  5. * Based on Sprinter and grbl.
  6. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. *
  21. */
  22. //todo: add support for multiple encoders on a single axis
  23. //todo: add z axis auto-leveling
  24. //todo: consolidate some of the related M codes?
  25. //todo: add endstop-replacement mode?
  26. //todo: try faster I2C speed; tweak TWI_FREQ (400000L, or faster?); or just TWBR = ((CPU_FREQ / 400000L) - 16) / 2;
  27. //todo: consider Marlin-optimized Wire library; i.e. MarlinWire, like MarlinSerial
  28. #include "../inc/MarlinConfig.h"
  29. #if ENABLED(I2C_POSITION_ENCODERS)
  30. #include "encoder_i2c.h"
  31. #include "../module/stepper.h"
  32. #include "../gcode/parser.h"
  33. #include "../feature/babystep.h"
  34. #include <Wire.h>
  35. I2CPositionEncodersMgr I2CPEM;
  36. void I2CPositionEncoder::init(const uint8_t address, const AxisEnum axis) {
  37. encoderAxis = axis;
  38. i2cAddress = address;
  39. initialized = true;
  40. SERIAL_ECHOLNPGM("Setting up encoder on ", AS_CHAR(axis_codes[encoderAxis]), " axis, addr = ", address);
  41. position = get_position();
  42. }
  43. void I2CPositionEncoder::update() {
  44. if (!initialized || !homed || !active) return; //check encoder is set up and active
  45. position = get_position();
  46. //we don't want to stop things just because the encoder missed a message,
  47. //so we only care about responses that indicate bad magnetic strength
  48. if (!passes_test(false)) { //check encoder data is good
  49. lastErrorTime = millis();
  50. /*
  51. if (trusted) { //commented out as part of the note below
  52. trusted = false;
  53. SERIAL_ECHOLNPGM("Fault detected on ", AS_CHAR(axis_codes[encoderAxis]), " axis encoder. Disengaging error correction until module is trusted again.");
  54. }
  55. */
  56. return;
  57. }
  58. if (!trusted) {
  59. /**
  60. * This is commented out because it introduces error and can cause bad print quality.
  61. *
  62. * This code is intended to manage situations where the encoder has reported bad magnetic strength.
  63. * This indicates that the magnetic strip was too far away from the sensor to reliably track position.
  64. * When this happens, this code resets the offset based on where the printer thinks it is. This has been
  65. * shown to introduce errors in actual position which result in drifting prints and poor print quality.
  66. * Perhaps a better method would be to disable correction on the axis with a problem, report it to the
  67. * user via the status leds on the encoder module and prompt the user to re-home the axis at which point
  68. * the encoder would be re-enabled.
  69. */
  70. #if 0
  71. // If the magnetic strength has been good for a certain time, start trusting the module again
  72. if (millis() - lastErrorTime > I2CPE_TIME_TRUSTED) {
  73. trusted = true;
  74. SERIAL_ECHOLNPGM("Untrusted encoder module on ", AS_CHAR(axis_codes[encoderAxis]), " axis has been fault-free for set duration, reinstating error correction.");
  75. //the encoder likely lost its place when the error occurred, so we'll reset and use the printer's
  76. //idea of where it the axis is to re-initialize
  77. const float pos = planner.get_axis_position_mm(encoderAxis);
  78. int32_t positionInTicks = pos * get_ticks_unit();
  79. //shift position from previous to current position
  80. zeroOffset -= (positionInTicks - get_position());
  81. #ifdef I2CPE_DEBUG
  82. SERIAL_ECHOLNPGM("Current position is ", pos);
  83. SERIAL_ECHOLNPGM("Position in encoder ticks is ", positionInTicks);
  84. SERIAL_ECHOLNPGM("New zero-offset of ", zeroOffset);
  85. SERIAL_ECHOPGM("New position reads as ", get_position());
  86. SERIAL_CHAR('(');
  87. SERIAL_DECIMAL(mm_from_count(get_position()));
  88. SERIAL_ECHOLNPGM(")");
  89. #endif
  90. }
  91. #endif
  92. return;
  93. }
  94. lastPosition = position;
  95. const millis_t positionTime = millis();
  96. //only do error correction if setup and enabled
  97. if (ec && ecMethod != I2CPE_ECM_NONE) {
  98. #ifdef I2CPE_EC_THRESH_PROPORTIONAL
  99. const millis_t deltaTime = positionTime - lastPositionTime;
  100. const uint32_t distance = ABS(position - lastPosition),
  101. speed = distance / deltaTime;
  102. const float threshold = constrain((speed / 50), 1, 50) * ecThreshold;
  103. #else
  104. const float threshold = get_error_correct_threshold();
  105. #endif
  106. //check error
  107. #if ENABLED(I2CPE_ERR_ROLLING_AVERAGE)
  108. float sum = 0, diffSum = 0;
  109. errIdx = (errIdx >= I2CPE_ERR_ARRAY_SIZE - 1) ? 0 : errIdx + 1;
  110. err[errIdx] = get_axis_error_steps(false);
  111. LOOP_L_N(i, I2CPE_ERR_ARRAY_SIZE) {
  112. sum += err[i];
  113. if (i) diffSum += ABS(err[i-1] - err[i]);
  114. }
  115. const int32_t error = int32_t(sum / (I2CPE_ERR_ARRAY_SIZE + 1)); //calculate average for error
  116. #else
  117. const int32_t error = get_axis_error_steps(false);
  118. #endif
  119. //SERIAL_ECHOLNPGM("Axis error steps: ", error);
  120. #ifdef I2CPE_ERR_THRESH_ABORT
  121. if (ABS(error) > I2CPE_ERR_THRESH_ABORT * planner.settings.axis_steps_per_mm[encoderAxis]) {
  122. //kill(PSTR("Significant Error"));
  123. SERIAL_ECHOLNPGM("Axis error over threshold, aborting!", error);
  124. safe_delay(5000);
  125. }
  126. #endif
  127. #if ENABLED(I2CPE_ERR_ROLLING_AVERAGE)
  128. if (errIdx == 0) {
  129. // In order to correct for "error" but avoid correcting for noise and non-skips
  130. // it must be > threshold and have a difference average of < 10 and be < 2000 steps
  131. if (ABS(error) > threshold * planner.settings.axis_steps_per_mm[encoderAxis]
  132. && diffSum < 10 * (I2CPE_ERR_ARRAY_SIZE - 1)
  133. && ABS(error) < 2000
  134. ) { // Check for persistent error (skip)
  135. errPrst[errPrstIdx++] = error; // Error must persist for I2CPE_ERR_PRST_ARRAY_SIZE error cycles. This also serves to improve the average accuracy
  136. if (errPrstIdx >= I2CPE_ERR_PRST_ARRAY_SIZE) {
  137. float sumP = 0;
  138. LOOP_L_N(i, I2CPE_ERR_PRST_ARRAY_SIZE) sumP += errPrst[i];
  139. const int32_t errorP = int32_t(sumP * RECIPROCAL(I2CPE_ERR_PRST_ARRAY_SIZE));
  140. SERIAL_CHAR(axis_codes[encoderAxis]);
  141. SERIAL_ECHOLNPGM(" : CORRECT ERR ", errorP * planner.mm_per_step[encoderAxis], "mm");
  142. babystep.add_steps(encoderAxis, -LROUND(errorP));
  143. errPrstIdx = 0;
  144. }
  145. }
  146. else
  147. errPrstIdx = 0;
  148. }
  149. #else
  150. if (ABS(error) > threshold * planner.settings.axis_steps_per_mm[encoderAxis]) {
  151. //SERIAL_ECHOLN(error);
  152. //SERIAL_ECHOLN(position);
  153. babystep.add_steps(encoderAxis, -LROUND(error / 2));
  154. }
  155. #endif
  156. if (ABS(error) > I2CPE_ERR_CNT_THRESH * planner.settings.axis_steps_per_mm[encoderAxis]) {
  157. const millis_t ms = millis();
  158. if (ELAPSED(ms, nextErrorCountTime)) {
  159. SERIAL_CHAR(axis_codes[encoderAxis]);
  160. SERIAL_ECHOLNPGM(" : LARGE ERR ", error, "; diffSum=", diffSum);
  161. errorCount++;
  162. nextErrorCountTime = ms + I2CPE_ERR_CNT_DEBOUNCE_MS;
  163. }
  164. }
  165. }
  166. lastPositionTime = positionTime;
  167. }
  168. void I2CPositionEncoder::set_homed() {
  169. if (active) {
  170. reset(); // Reset module's offset to zero (so current position is homed / zero)
  171. delay(10);
  172. zeroOffset = get_raw_count();
  173. homed = trusted = true;
  174. #ifdef I2CPE_DEBUG
  175. SERIAL_CHAR(axis_codes[encoderAxis]);
  176. SERIAL_ECHOLNPGM(" axis encoder homed, offset of ", zeroOffset, " ticks.");
  177. #endif
  178. }
  179. }
  180. void I2CPositionEncoder::set_unhomed() {
  181. zeroOffset = 0;
  182. homed = trusted = false;
  183. #ifdef I2CPE_DEBUG
  184. SERIAL_CHAR(axis_codes[encoderAxis]);
  185. SERIAL_ECHOLNPGM(" axis encoder unhomed.");
  186. #endif
  187. }
  188. bool I2CPositionEncoder::passes_test(const bool report) {
  189. if (report) {
  190. if (H != I2CPE_MAG_SIG_GOOD) SERIAL_ECHOPGM("Warning. ");
  191. SERIAL_CHAR(axis_codes[encoderAxis]);
  192. serial_ternary(H == I2CPE_MAG_SIG_BAD, F(" axis "), F("magnetic strip "), F("encoder "));
  193. switch (H) {
  194. case I2CPE_MAG_SIG_GOOD:
  195. case I2CPE_MAG_SIG_MID:
  196. SERIAL_ECHO_TERNARY(H == I2CPE_MAG_SIG_GOOD, "passes test; field strength ", "good", "fair", ".\n");
  197. break;
  198. default:
  199. SERIAL_ECHOLNPGM("not detected!");
  200. }
  201. }
  202. return (H == I2CPE_MAG_SIG_GOOD || H == I2CPE_MAG_SIG_MID);
  203. }
  204. float I2CPositionEncoder::get_axis_error_mm(const bool report) {
  205. const float target = planner.get_axis_position_mm(encoderAxis),
  206. actual = mm_from_count(position),
  207. diff = actual - target,
  208. error = ABS(diff) > 10000 ? 0 : diff; // Huge error is a bad reading
  209. if (report) {
  210. SERIAL_CHAR(axis_codes[encoderAxis]);
  211. SERIAL_ECHOLNPGM(" axis target=", target, "mm; actual=", actual, "mm; err=", error, "mm");
  212. }
  213. return error;
  214. }
  215. int32_t I2CPositionEncoder::get_axis_error_steps(const bool report) {
  216. if (!active) {
  217. if (report) {
  218. SERIAL_CHAR(axis_codes[encoderAxis]);
  219. SERIAL_ECHOLNPGM(" axis encoder not active!");
  220. }
  221. return 0;
  222. }
  223. float stepperTicksPerUnit;
  224. int32_t encoderTicks = position, encoderCountInStepperTicksScaled;
  225. //int32_t stepperTicks = stepper.position(encoderAxis);
  226. // With a rotary encoder we're concerned with ticks/rev; whereas with a linear we're concerned with ticks/mm
  227. stepperTicksPerUnit = (type == I2CPE_ENC_TYPE_ROTARY) ? stepperTicks : planner.settings.axis_steps_per_mm[encoderAxis];
  228. //convert both 'ticks' into same units / base
  229. encoderCountInStepperTicksScaled = LROUND((stepperTicksPerUnit * encoderTicks) / encoderTicksPerUnit);
  230. const int32_t target = stepper.position(encoderAxis);
  231. int32_t error = encoderCountInStepperTicksScaled - target;
  232. //suppress discontinuities (might be caused by bad I2C readings...?)
  233. const bool suppressOutput = (ABS(error - errorPrev) > 100);
  234. errorPrev = error;
  235. if (report) {
  236. SERIAL_CHAR(axis_codes[encoderAxis]);
  237. SERIAL_ECHOLNPGM(" axis target=", target, "; actual=", encoderCountInStepperTicksScaled, "; err=", error);
  238. }
  239. if (suppressOutput) {
  240. if (report) SERIAL_ECHOLNPGM("!Discontinuity. Suppressing error.");
  241. error = 0;
  242. }
  243. return error;
  244. }
  245. int32_t I2CPositionEncoder::get_raw_count() {
  246. uint8_t index = 0;
  247. i2cLong encoderCount;
  248. encoderCount.val = 0x00;
  249. if (Wire.requestFrom(I2C_ADDRESS(i2cAddress), uint8_t(3)) != 3) {
  250. //houston, we have a problem...
  251. H = I2CPE_MAG_SIG_NF;
  252. return 0;
  253. }
  254. while (Wire.available())
  255. encoderCount.bval[index++] = (uint8_t)Wire.read();
  256. //extract the magnetic strength
  257. H = (B00000011 & (encoderCount.bval[2] >> 6));
  258. //extract sign bit; sign = (encoderCount.bval[2] & B00100000);
  259. //set all upper bits to the sign value to overwrite H
  260. encoderCount.val = (encoderCount.bval[2] & B00100000) ? (encoderCount.val | 0xFFC00000) : (encoderCount.val & 0x003FFFFF);
  261. if (invert) encoderCount.val *= -1;
  262. return encoderCount.val;
  263. }
  264. bool I2CPositionEncoder::test_axis() {
  265. // Only works on XYZ Cartesian machines for the time being
  266. if (!(encoderAxis == X_AXIS || encoderAxis == Y_AXIS || encoderAxis == Z_AXIS)) return false;
  267. const float startPosition = soft_endstop.min[encoderAxis] + 10,
  268. endPosition = soft_endstop.max[encoderAxis] - 10;
  269. const feedRate_t fr_mm_s = FLOOR(homing_feedrate(encoderAxis));
  270. ec = false;
  271. xyze_pos_t startCoord, endCoord;
  272. LOOP_LINEAR_AXES(a) {
  273. startCoord[a] = planner.get_axis_position_mm((AxisEnum)a);
  274. endCoord[a] = planner.get_axis_position_mm((AxisEnum)a);
  275. }
  276. startCoord[encoderAxis] = startPosition;
  277. endCoord[encoderAxis] = endPosition;
  278. planner.synchronize();
  279. #if HAS_EXTRUDERS
  280. startCoord.e = planner.get_axis_position_mm(E_AXIS);
  281. planner.buffer_line(startCoord, fr_mm_s, 0);
  282. planner.synchronize();
  283. #endif
  284. // if the module isn't currently trusted, wait until it is (or until it should be if things are working)
  285. if (!trusted) {
  286. int32_t startWaitingTime = millis();
  287. while (!trusted && millis() - startWaitingTime < I2CPE_TIME_TRUSTED)
  288. safe_delay(500);
  289. }
  290. if (trusted) { // if trusted, commence test
  291. TERN_(HAS_EXTRUDERS, endCoord.e = planner.get_axis_position_mm(E_AXIS));
  292. planner.buffer_line(endCoord, fr_mm_s, 0);
  293. planner.synchronize();
  294. }
  295. return trusted;
  296. }
  297. void I2CPositionEncoder::calibrate_steps_mm(const uint8_t iter) {
  298. if (type != I2CPE_ENC_TYPE_LINEAR) {
  299. SERIAL_ECHOLNPGM("Steps/mm calibration requires linear encoder.");
  300. return;
  301. }
  302. if (!(encoderAxis == X_AXIS || encoderAxis == Y_AXIS || encoderAxis == Z_AXIS)) {
  303. SERIAL_ECHOLNPGM("Steps/mm calibration not supported for this axis.");
  304. return;
  305. }
  306. float old_steps_mm, new_steps_mm,
  307. startDistance, endDistance,
  308. travelDistance, travelledDistance, total = 0;
  309. int32_t startCount, stopCount;
  310. const feedRate_t fr_mm_s = homing_feedrate(encoderAxis);
  311. bool oldec = ec;
  312. ec = false;
  313. startDistance = 20;
  314. endDistance = soft_endstop.max[encoderAxis] - 20;
  315. travelDistance = endDistance - startDistance;
  316. xyze_pos_t startCoord, endCoord;
  317. LOOP_LINEAR_AXES(a) {
  318. startCoord[a] = planner.get_axis_position_mm((AxisEnum)a);
  319. endCoord[a] = planner.get_axis_position_mm((AxisEnum)a);
  320. }
  321. startCoord[encoderAxis] = startDistance;
  322. endCoord[encoderAxis] = endDistance;
  323. planner.synchronize();
  324. LOOP_L_N(i, iter) {
  325. TERN_(HAS_EXTRUDERS, startCoord.e = planner.get_axis_position_mm(E_AXIS));
  326. planner.buffer_line(startCoord, fr_mm_s, 0);
  327. planner.synchronize();
  328. delay(250);
  329. startCount = get_position();
  330. //do_blocking_move_to(endCoord);
  331. TERN_(HAS_EXTRUDERS, endCoord.e = planner.get_axis_position_mm(E_AXIS));
  332. planner.buffer_line(endCoord, fr_mm_s, 0);
  333. planner.synchronize();
  334. //Read encoder distance
  335. delay(250);
  336. stopCount = get_position();
  337. travelledDistance = mm_from_count(ABS(stopCount - startCount));
  338. SERIAL_ECHOLNPGM("Attempted travel: ", travelDistance, "mm");
  339. SERIAL_ECHOLNPGM(" Actual travel: ", travelledDistance, "mm");
  340. //Calculate new axis steps per unit
  341. old_steps_mm = planner.settings.axis_steps_per_mm[encoderAxis];
  342. new_steps_mm = (old_steps_mm * travelDistance) / travelledDistance;
  343. SERIAL_ECHOLNPGM("Old steps/mm: ", old_steps_mm);
  344. SERIAL_ECHOLNPGM("New steps/mm: ", new_steps_mm);
  345. //Save new value
  346. planner.settings.axis_steps_per_mm[encoderAxis] = new_steps_mm;
  347. if (iter > 1) {
  348. total += new_steps_mm;
  349. // swap start and end points so next loop runs from current position
  350. const float tempCoord = startCoord[encoderAxis];
  351. startCoord[encoderAxis] = endCoord[encoderAxis];
  352. endCoord[encoderAxis] = tempCoord;
  353. }
  354. }
  355. if (iter > 1) {
  356. total /= (float)iter;
  357. SERIAL_ECHOLNPGM("Average steps/mm: ", total);
  358. }
  359. ec = oldec;
  360. SERIAL_ECHOLNPGM("Calculated steps/mm set. Use M500 to save to EEPROM.");
  361. }
  362. void I2CPositionEncoder::reset() {
  363. Wire.beginTransmission(I2C_ADDRESS(i2cAddress));
  364. Wire.write(I2CPE_RESET_COUNT);
  365. Wire.endTransmission();
  366. TERN_(I2CPE_ERR_ROLLING_AVERAGE, ZERO(err));
  367. }
  368. bool I2CPositionEncodersMgr::I2CPE_anyaxis;
  369. uint8_t I2CPositionEncodersMgr::I2CPE_addr,
  370. I2CPositionEncodersMgr::I2CPE_idx;
  371. I2CPositionEncoder I2CPositionEncodersMgr::encoders[I2CPE_ENCODER_CNT];
  372. void I2CPositionEncodersMgr::init() {
  373. Wire.begin();
  374. #if I2CPE_ENCODER_CNT > 0
  375. uint8_t i = 0;
  376. encoders[i].init(I2CPE_ENC_1_ADDR, I2CPE_ENC_1_AXIS);
  377. #ifdef I2CPE_ENC_1_TYPE
  378. encoders[i].set_type(I2CPE_ENC_1_TYPE);
  379. #endif
  380. #ifdef I2CPE_ENC_1_TICKS_UNIT
  381. encoders[i].set_ticks_unit(I2CPE_ENC_1_TICKS_UNIT);
  382. #endif
  383. #ifdef I2CPE_ENC_1_TICKS_REV
  384. encoders[i].set_stepper_ticks(I2CPE_ENC_1_TICKS_REV);
  385. #endif
  386. #ifdef I2CPE_ENC_1_INVERT
  387. encoders[i].set_inverted(I2CPE_ENC_1_INVERT);
  388. #endif
  389. #ifdef I2CPE_ENC_1_EC_METHOD
  390. encoders[i].set_ec_method(I2CPE_ENC_1_EC_METHOD);
  391. #endif
  392. #ifdef I2CPE_ENC_1_EC_THRESH
  393. encoders[i].set_ec_threshold(I2CPE_ENC_1_EC_THRESH);
  394. #endif
  395. encoders[i].set_active(encoders[i].passes_test(true));
  396. TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_1_AXIS == E_AXIS) encoders[i].set_homed());
  397. #endif
  398. #if I2CPE_ENCODER_CNT > 1
  399. i++;
  400. encoders[i].init(I2CPE_ENC_2_ADDR, I2CPE_ENC_2_AXIS);
  401. #ifdef I2CPE_ENC_2_TYPE
  402. encoders[i].set_type(I2CPE_ENC_2_TYPE);
  403. #endif
  404. #ifdef I2CPE_ENC_2_TICKS_UNIT
  405. encoders[i].set_ticks_unit(I2CPE_ENC_2_TICKS_UNIT);
  406. #endif
  407. #ifdef I2CPE_ENC_2_TICKS_REV
  408. encoders[i].set_stepper_ticks(I2CPE_ENC_2_TICKS_REV);
  409. #endif
  410. #ifdef I2CPE_ENC_2_INVERT
  411. encoders[i].set_inverted(I2CPE_ENC_2_INVERT);
  412. #endif
  413. #ifdef I2CPE_ENC_2_EC_METHOD
  414. encoders[i].set_ec_method(I2CPE_ENC_2_EC_METHOD);
  415. #endif
  416. #ifdef I2CPE_ENC_2_EC_THRESH
  417. encoders[i].set_ec_threshold(I2CPE_ENC_2_EC_THRESH);
  418. #endif
  419. encoders[i].set_active(encoders[i].passes_test(true));
  420. TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_2_AXIS == E_AXIS) encoders[i].set_homed());
  421. #endif
  422. #if I2CPE_ENCODER_CNT > 2
  423. i++;
  424. encoders[i].init(I2CPE_ENC_3_ADDR, I2CPE_ENC_3_AXIS);
  425. #ifdef I2CPE_ENC_3_TYPE
  426. encoders[i].set_type(I2CPE_ENC_3_TYPE);
  427. #endif
  428. #ifdef I2CPE_ENC_3_TICKS_UNIT
  429. encoders[i].set_ticks_unit(I2CPE_ENC_3_TICKS_UNIT);
  430. #endif
  431. #ifdef I2CPE_ENC_3_TICKS_REV
  432. encoders[i].set_stepper_ticks(I2CPE_ENC_3_TICKS_REV);
  433. #endif
  434. #ifdef I2CPE_ENC_3_INVERT
  435. encoders[i].set_inverted(I2CPE_ENC_3_INVERT);
  436. #endif
  437. #ifdef I2CPE_ENC_3_EC_METHOD
  438. encoders[i].set_ec_method(I2CPE_ENC_3_EC_METHOD);
  439. #endif
  440. #ifdef I2CPE_ENC_3_EC_THRESH
  441. encoders[i].set_ec_threshold(I2CPE_ENC_3_EC_THRESH);
  442. #endif
  443. encoders[i].set_active(encoders[i].passes_test(true));
  444. TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_3_AXIS == E_AXIS) encoders[i].set_homed());
  445. #endif
  446. #if I2CPE_ENCODER_CNT > 3
  447. i++;
  448. encoders[i].init(I2CPE_ENC_4_ADDR, I2CPE_ENC_4_AXIS);
  449. #ifdef I2CPE_ENC_4_TYPE
  450. encoders[i].set_type(I2CPE_ENC_4_TYPE);
  451. #endif
  452. #ifdef I2CPE_ENC_4_TICKS_UNIT
  453. encoders[i].set_ticks_unit(I2CPE_ENC_4_TICKS_UNIT);
  454. #endif
  455. #ifdef I2CPE_ENC_4_TICKS_REV
  456. encoders[i].set_stepper_ticks(I2CPE_ENC_4_TICKS_REV);
  457. #endif
  458. #ifdef I2CPE_ENC_4_INVERT
  459. encoders[i].set_inverted(I2CPE_ENC_4_INVERT);
  460. #endif
  461. #ifdef I2CPE_ENC_4_EC_METHOD
  462. encoders[i].set_ec_method(I2CPE_ENC_4_EC_METHOD);
  463. #endif
  464. #ifdef I2CPE_ENC_4_EC_THRESH
  465. encoders[i].set_ec_threshold(I2CPE_ENC_4_EC_THRESH);
  466. #endif
  467. encoders[i].set_active(encoders[i].passes_test(true));
  468. TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_4_AXIS == E_AXIS) encoders[i].set_homed());
  469. #endif
  470. #if I2CPE_ENCODER_CNT > 4
  471. i++;
  472. encoders[i].init(I2CPE_ENC_5_ADDR, I2CPE_ENC_5_AXIS);
  473. #ifdef I2CPE_ENC_5_TYPE
  474. encoders[i].set_type(I2CPE_ENC_5_TYPE);
  475. #endif
  476. #ifdef I2CPE_ENC_5_TICKS_UNIT
  477. encoders[i].set_ticks_unit(I2CPE_ENC_5_TICKS_UNIT);
  478. #endif
  479. #ifdef I2CPE_ENC_5_TICKS_REV
  480. encoders[i].set_stepper_ticks(I2CPE_ENC_5_TICKS_REV);
  481. #endif
  482. #ifdef I2CPE_ENC_5_INVERT
  483. encoders[i].set_inverted(I2CPE_ENC_5_INVERT);
  484. #endif
  485. #ifdef I2CPE_ENC_5_EC_METHOD
  486. encoders[i].set_ec_method(I2CPE_ENC_5_EC_METHOD);
  487. #endif
  488. #ifdef I2CPE_ENC_5_EC_THRESH
  489. encoders[i].set_ec_threshold(I2CPE_ENC_5_EC_THRESH);
  490. #endif
  491. encoders[i].set_active(encoders[i].passes_test(true));
  492. TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_5_AXIS == E_AXIS) encoders[i].set_homed());
  493. #endif
  494. #if I2CPE_ENCODER_CNT > 5
  495. i++;
  496. encoders[i].init(I2CPE_ENC_6_ADDR, I2CPE_ENC_6_AXIS);
  497. #ifdef I2CPE_ENC_6_TYPE
  498. encoders[i].set_type(I2CPE_ENC_6_TYPE);
  499. #endif
  500. #ifdef I2CPE_ENC_6_TICKS_UNIT
  501. encoders[i].set_ticks_unit(I2CPE_ENC_6_TICKS_UNIT);
  502. #endif
  503. #ifdef I2CPE_ENC_6_TICKS_REV
  504. encoders[i].set_stepper_ticks(I2CPE_ENC_6_TICKS_REV);
  505. #endif
  506. #ifdef I2CPE_ENC_6_INVERT
  507. encoders[i].set_inverted(I2CPE_ENC_6_INVERT);
  508. #endif
  509. #ifdef I2CPE_ENC_6_EC_METHOD
  510. encoders[i].set_ec_method(I2CPE_ENC_6_EC_METHOD);
  511. #endif
  512. #ifdef I2CPE_ENC_6_EC_THRESH
  513. encoders[i].set_ec_threshold(I2CPE_ENC_6_EC_THRESH);
  514. #endif
  515. encoders[i].set_active(encoders[i].passes_test(true));
  516. TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_6_AXIS == E_AXIS) encoders[i].set_homed());
  517. #endif
  518. }
  519. void I2CPositionEncodersMgr::report_position(const int8_t idx, const bool units, const bool noOffset) {
  520. CHECK_IDX();
  521. if (units)
  522. SERIAL_ECHOLN(noOffset ? encoders[idx].mm_from_count(encoders[idx].get_raw_count()) : encoders[idx].get_position_mm());
  523. else {
  524. if (noOffset) {
  525. const int32_t raw_count = encoders[idx].get_raw_count();
  526. SERIAL_CHAR(axis_codes[encoders[idx].get_axis()], ' ');
  527. for (uint8_t j = 31; j > 0; j--)
  528. SERIAL_ECHO((bool)(0x00000001 & (raw_count >> j)));
  529. SERIAL_ECHO((bool)(0x00000001 & raw_count));
  530. SERIAL_CHAR(' ');
  531. SERIAL_ECHOLN(raw_count);
  532. }
  533. else
  534. SERIAL_ECHOLN(encoders[idx].get_position());
  535. }
  536. }
  537. void I2CPositionEncodersMgr::change_module_address(const uint8_t oldaddr, const uint8_t newaddr) {
  538. // First check 'new' address is not in use
  539. Wire.beginTransmission(I2C_ADDRESS(newaddr));
  540. if (!Wire.endTransmission()) {
  541. SERIAL_ECHOLNPGM("?There is already a device with that address on the I2C bus! (", newaddr, ")");
  542. return;
  543. }
  544. // Now check that we can find the module on the oldaddr address
  545. Wire.beginTransmission(I2C_ADDRESS(oldaddr));
  546. if (Wire.endTransmission()) {
  547. SERIAL_ECHOLNPGM("?No module detected at this address! (", oldaddr, ")");
  548. return;
  549. }
  550. SERIAL_ECHOLNPGM("Module found at ", oldaddr, ", changing address to ", newaddr);
  551. // Change the modules address
  552. Wire.beginTransmission(I2C_ADDRESS(oldaddr));
  553. Wire.write(I2CPE_SET_ADDR);
  554. Wire.write(newaddr);
  555. Wire.endTransmission();
  556. SERIAL_ECHOLNPGM("Address changed, resetting and waiting for confirmation..");
  557. // Wait for the module to reset (can probably be improved by polling address with a timeout).
  558. safe_delay(I2CPE_REBOOT_TIME);
  559. // Look for the module at the new address.
  560. Wire.beginTransmission(I2C_ADDRESS(newaddr));
  561. if (Wire.endTransmission()) {
  562. SERIAL_ECHOLNPGM("Address change failed! Check encoder module.");
  563. return;
  564. }
  565. SERIAL_ECHOLNPGM("Address change successful!");
  566. // Now, if this module is configured, find which encoder instance it's supposed to correspond to
  567. // and enable it (it will likely have failed initialization on power-up, before the address change).
  568. const int8_t idx = idx_from_addr(newaddr);
  569. if (idx >= 0 && !encoders[idx].get_active()) {
  570. SERIAL_CHAR(axis_codes[encoders[idx].get_axis()]);
  571. SERIAL_ECHOLNPGM(" axis encoder was not detected on printer startup. Trying again.");
  572. encoders[idx].set_active(encoders[idx].passes_test(true));
  573. }
  574. }
  575. void I2CPositionEncodersMgr::report_module_firmware(const uint8_t address) {
  576. // First check there is a module
  577. Wire.beginTransmission(I2C_ADDRESS(address));
  578. if (Wire.endTransmission()) {
  579. SERIAL_ECHOLNPGM("?No module detected at this address! (", address, ")");
  580. return;
  581. }
  582. SERIAL_ECHOLNPGM("Requesting version info from module at address ", address, ":");
  583. Wire.beginTransmission(I2C_ADDRESS(address));
  584. Wire.write(I2CPE_SET_REPORT_MODE);
  585. Wire.write(I2CPE_REPORT_VERSION);
  586. Wire.endTransmission();
  587. // Read value
  588. if (Wire.requestFrom(I2C_ADDRESS(address), uint8_t(32))) {
  589. char c;
  590. while (Wire.available() > 0 && (c = (char)Wire.read()) > 0)
  591. SERIAL_CHAR(c);
  592. SERIAL_EOL();
  593. }
  594. // Set module back to normal (distance) mode
  595. Wire.beginTransmission(I2C_ADDRESS(address));
  596. Wire.write(I2CPE_SET_REPORT_MODE);
  597. Wire.write(I2CPE_REPORT_DISTANCE);
  598. Wire.endTransmission();
  599. }
  600. int8_t I2CPositionEncodersMgr::parse() {
  601. I2CPE_addr = 0;
  602. if (parser.seen('A')) {
  603. if (!parser.has_value()) {
  604. SERIAL_ECHOLNPGM("?A seen, but no address specified! [30-200]");
  605. return I2CPE_PARSE_ERR;
  606. };
  607. I2CPE_addr = parser.value_byte();
  608. if (!WITHIN(I2CPE_addr, 30, 200)) { // reserve the first 30 and last 55
  609. SERIAL_ECHOLNPGM("?Address out of range. [30-200]");
  610. return I2CPE_PARSE_ERR;
  611. }
  612. I2CPE_idx = idx_from_addr(I2CPE_addr);
  613. if (I2CPE_idx >= I2CPE_ENCODER_CNT) {
  614. SERIAL_ECHOLNPGM("?No device with this address!");
  615. return I2CPE_PARSE_ERR;
  616. }
  617. }
  618. else if (parser.seenval('I')) {
  619. if (!parser.has_value()) {
  620. SERIAL_ECHOLNPGM("?I seen, but no index specified! [0-", I2CPE_ENCODER_CNT - 1, "]");
  621. return I2CPE_PARSE_ERR;
  622. };
  623. I2CPE_idx = parser.value_byte();
  624. if (I2CPE_idx >= I2CPE_ENCODER_CNT) {
  625. SERIAL_ECHOLNPGM("?Index out of range. [0-", I2CPE_ENCODER_CNT - 1, "]");
  626. return I2CPE_PARSE_ERR;
  627. }
  628. I2CPE_addr = encoders[I2CPE_idx].get_address();
  629. }
  630. else
  631. I2CPE_idx = 0xFF;
  632. I2CPE_anyaxis = parser.seen_axis();
  633. return I2CPE_PARSE_OK;
  634. };
  635. /**
  636. * M860: Report the position(s) of position encoder module(s).
  637. *
  638. * A<addr> Module I2C address. [30, 200].
  639. * I<index> Module index. [0, I2CPE_ENCODER_CNT - 1]
  640. * O Include homed zero-offset in returned position.
  641. * U Units in mm or raw step count.
  642. *
  643. * If A or I not specified:
  644. * X Report on X axis encoder, if present.
  645. * Y Report on Y axis encoder, if present.
  646. * Z Report on Z axis encoder, if present.
  647. * E Report on E axis encoder, if present.
  648. */
  649. void I2CPositionEncodersMgr::M860() {
  650. if (parse()) return;
  651. const bool hasU = parser.seen_test('U'), hasO = parser.seen_test('O');
  652. if (I2CPE_idx == 0xFF) {
  653. LOOP_LOGICAL_AXES(i) {
  654. if (!I2CPE_anyaxis || parser.seen_test(axis_codes[i])) {
  655. const uint8_t idx = idx_from_axis(AxisEnum(i));
  656. if ((int8_t)idx >= 0) report_position(idx, hasU, hasO);
  657. }
  658. }
  659. }
  660. else
  661. report_position(I2CPE_idx, hasU, hasO);
  662. }
  663. /**
  664. * M861: Report the status of position encoder modules.
  665. *
  666. * A<addr> Module I2C address. [30, 200].
  667. * I<index> Module index. [0, I2CPE_ENCODER_CNT - 1]
  668. *
  669. * If A or I not specified:
  670. * X Report on X axis encoder, if present.
  671. * Y Report on Y axis encoder, if present.
  672. * Z Report on Z axis encoder, if present.
  673. * E Report on E axis encoder, if present.
  674. */
  675. void I2CPositionEncodersMgr::M861() {
  676. if (parse()) return;
  677. if (I2CPE_idx == 0xFF) {
  678. LOOP_LOGICAL_AXES(i) {
  679. if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
  680. const uint8_t idx = idx_from_axis(AxisEnum(i));
  681. if ((int8_t)idx >= 0) report_status(idx);
  682. }
  683. }
  684. }
  685. else
  686. report_status(I2CPE_idx);
  687. }
  688. /**
  689. * M862: Perform an axis continuity test for position encoder
  690. * modules.
  691. *
  692. * A<addr> Module I2C address. [30, 200].
  693. * I<index> Module index. [0, I2CPE_ENCODER_CNT - 1]
  694. *
  695. * If A or I not specified:
  696. * X Report on X axis encoder, if present.
  697. * Y Report on Y axis encoder, if present.
  698. * Z Report on Z axis encoder, if present.
  699. * E Report on E axis encoder, if present.
  700. */
  701. void I2CPositionEncodersMgr::M862() {
  702. if (parse()) return;
  703. if (I2CPE_idx == 0xFF) {
  704. LOOP_LOGICAL_AXES(i) {
  705. if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
  706. const uint8_t idx = idx_from_axis(AxisEnum(i));
  707. if ((int8_t)idx >= 0) test_axis(idx);
  708. }
  709. }
  710. }
  711. else
  712. test_axis(I2CPE_idx);
  713. }
  714. /**
  715. * M863: Perform steps-per-mm calibration for
  716. * position encoder modules.
  717. *
  718. * A<addr> Module I2C address. [30, 200].
  719. * I<index> Module index. [0, I2CPE_ENCODER_CNT - 1]
  720. * P Number of rePeats/iterations.
  721. *
  722. * If A or I not specified:
  723. * X Report on X axis encoder, if present.
  724. * Y Report on Y axis encoder, if present.
  725. * Z Report on Z axis encoder, if present.
  726. * E Report on E axis encoder, if present.
  727. */
  728. void I2CPositionEncodersMgr::M863() {
  729. if (parse()) return;
  730. const uint8_t iterations = constrain(parser.byteval('P', 1), 1, 10);
  731. if (I2CPE_idx == 0xFF) {
  732. LOOP_LOGICAL_AXES(i) {
  733. if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
  734. const uint8_t idx = idx_from_axis(AxisEnum(i));
  735. if ((int8_t)idx >= 0) calibrate_steps_mm(idx, iterations);
  736. }
  737. }
  738. }
  739. else
  740. calibrate_steps_mm(I2CPE_idx, iterations);
  741. }
  742. /**
  743. * M864: Change position encoder module I2C address.
  744. *
  745. * A<addr> Module current/old I2C address. If not present,
  746. * assumes default address (030). [30, 200].
  747. * S<addr> Module new I2C address. [30, 200].
  748. *
  749. * If S is not specified:
  750. * X Use I2CPE_PRESET_ADDR_X (030).
  751. * Y Use I2CPE_PRESET_ADDR_Y (031).
  752. * Z Use I2CPE_PRESET_ADDR_Z (032).
  753. * E Use I2CPE_PRESET_ADDR_E (033).
  754. */
  755. void I2CPositionEncodersMgr::M864() {
  756. uint8_t newAddress;
  757. if (parse()) return;
  758. if (!I2CPE_addr) I2CPE_addr = I2CPE_PRESET_ADDR_X;
  759. if (parser.seen('S')) {
  760. if (!parser.has_value()) {
  761. SERIAL_ECHOLNPGM("?S seen, but no address specified! [30-200]");
  762. return;
  763. };
  764. newAddress = parser.value_byte();
  765. if (!WITHIN(newAddress, 30, 200)) {
  766. SERIAL_ECHOLNPGM("?New address out of range. [30-200]");
  767. return;
  768. }
  769. }
  770. else if (!I2CPE_anyaxis) {
  771. SERIAL_ECHOLNPGM("?You must specify S or [XYZE].");
  772. return;
  773. }
  774. else {
  775. if (parser.seen_test('X')) newAddress = I2CPE_PRESET_ADDR_X;
  776. else if (parser.seen_test('Y')) newAddress = I2CPE_PRESET_ADDR_Y;
  777. else if (parser.seen_test('Z')) newAddress = I2CPE_PRESET_ADDR_Z;
  778. else if (parser.seen_test('E')) newAddress = I2CPE_PRESET_ADDR_E;
  779. else return;
  780. }
  781. SERIAL_ECHOLNPGM("Changing module at address ", I2CPE_addr, " to address ", newAddress);
  782. change_module_address(I2CPE_addr, newAddress);
  783. }
  784. /**
  785. * M865: Check position encoder module firmware version.
  786. *
  787. * A<addr> Module I2C address. [30, 200].
  788. * I<index> Module index. [0, I2CPE_ENCODER_CNT - 1].
  789. *
  790. * If A or I not specified:
  791. * X Check X axis encoder, if present.
  792. * Y Check Y axis encoder, if present.
  793. * Z Check Z axis encoder, if present.
  794. * E Check E axis encoder, if present.
  795. */
  796. void I2CPositionEncodersMgr::M865() {
  797. if (parse()) return;
  798. if (!I2CPE_addr) {
  799. LOOP_LOGICAL_AXES(i) {
  800. if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
  801. const uint8_t idx = idx_from_axis(AxisEnum(i));
  802. if ((int8_t)idx >= 0) report_module_firmware(encoders[idx].get_address());
  803. }
  804. }
  805. }
  806. else
  807. report_module_firmware(I2CPE_addr);
  808. }
  809. /**
  810. * M866: Report or reset position encoder module error
  811. * count.
  812. *
  813. * A<addr> Module I2C address. [30, 200].
  814. * I<index> Module index. [0, I2CPE_ENCODER_CNT - 1].
  815. * R Reset error counter.
  816. *
  817. * If A or I not specified:
  818. * X Act on X axis encoder, if present.
  819. * Y Act on Y axis encoder, if present.
  820. * Z Act on Z axis encoder, if present.
  821. * E Act on E axis encoder, if present.
  822. */
  823. void I2CPositionEncodersMgr::M866() {
  824. if (parse()) return;
  825. const bool hasR = parser.seen_test('R');
  826. if (I2CPE_idx == 0xFF) {
  827. LOOP_LOGICAL_AXES(i) {
  828. if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
  829. const uint8_t idx = idx_from_axis(AxisEnum(i));
  830. if ((int8_t)idx >= 0) {
  831. if (hasR)
  832. reset_error_count(idx, AxisEnum(i));
  833. else
  834. report_error_count(idx, AxisEnum(i));
  835. }
  836. }
  837. }
  838. }
  839. else if (hasR)
  840. reset_error_count(I2CPE_idx, encoders[I2CPE_idx].get_axis());
  841. else
  842. report_error_count(I2CPE_idx, encoders[I2CPE_idx].get_axis());
  843. }
  844. /**
  845. * M867: Enable/disable or toggle error correction for position encoder modules.
  846. *
  847. * A<addr> Module I2C address. [30, 200].
  848. * I<index> Module index. [0, I2CPE_ENCODER_CNT - 1].
  849. * S<1|0> Enable/disable error correction. 1 enables, 0 disables. If not
  850. * supplied, toggle.
  851. *
  852. * If A or I not specified:
  853. * X Act on X axis encoder, if present.
  854. * Y Act on Y axis encoder, if present.
  855. * Z Act on Z axis encoder, if present.
  856. * E Act on E axis encoder, if present.
  857. */
  858. void I2CPositionEncodersMgr::M867() {
  859. if (parse()) return;
  860. const int8_t onoff = parser.seenval('S') ? parser.value_int() : -1;
  861. if (I2CPE_idx == 0xFF) {
  862. LOOP_LOGICAL_AXES(i) {
  863. if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
  864. const uint8_t idx = idx_from_axis(AxisEnum(i));
  865. if ((int8_t)idx >= 0) {
  866. const bool ena = onoff == -1 ? !encoders[I2CPE_idx].get_ec_enabled() : !!onoff;
  867. enable_ec(idx, ena, AxisEnum(i));
  868. }
  869. }
  870. }
  871. }
  872. else {
  873. const bool ena = onoff == -1 ? !encoders[I2CPE_idx].get_ec_enabled() : !!onoff;
  874. enable_ec(I2CPE_idx, ena, encoders[I2CPE_idx].get_axis());
  875. }
  876. }
  877. /**
  878. * M868: Report or set position encoder module error correction
  879. * threshold.
  880. *
  881. * A<addr> Module I2C address. [30, 200].
  882. * I<index> Module index. [0, I2CPE_ENCODER_CNT - 1].
  883. * T New error correction threshold.
  884. *
  885. * If A not specified:
  886. * X Act on X axis encoder, if present.
  887. * Y Act on Y axis encoder, if present.
  888. * Z Act on Z axis encoder, if present.
  889. * E Act on E axis encoder, if present.
  890. */
  891. void I2CPositionEncodersMgr::M868() {
  892. if (parse()) return;
  893. const float newThreshold = parser.seenval('T') ? parser.value_float() : -9999;
  894. if (I2CPE_idx == 0xFF) {
  895. LOOP_LOGICAL_AXES(i) {
  896. if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
  897. const uint8_t idx = idx_from_axis(AxisEnum(i));
  898. if ((int8_t)idx >= 0) {
  899. if (newThreshold != -9999)
  900. set_ec_threshold(idx, newThreshold, encoders[idx].get_axis());
  901. else
  902. get_ec_threshold(idx, encoders[idx].get_axis());
  903. }
  904. }
  905. }
  906. }
  907. else if (newThreshold != -9999)
  908. set_ec_threshold(I2CPE_idx, newThreshold, encoders[I2CPE_idx].get_axis());
  909. else
  910. get_ec_threshold(I2CPE_idx, encoders[I2CPE_idx].get_axis());
  911. }
  912. /**
  913. * M869: Report position encoder module error.
  914. *
  915. * A<addr> Module I2C address. [30, 200].
  916. * I<index> Module index. [0, I2CPE_ENCODER_CNT - 1].
  917. *
  918. * If A not specified:
  919. * X Act on X axis encoder, if present.
  920. * Y Act on Y axis encoder, if present.
  921. * Z Act on Z axis encoder, if present.
  922. * E Act on E axis encoder, if present.
  923. */
  924. void I2CPositionEncodersMgr::M869() {
  925. if (parse()) return;
  926. if (I2CPE_idx == 0xFF) {
  927. LOOP_LOGICAL_AXES(i) {
  928. if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
  929. const uint8_t idx = idx_from_axis(AxisEnum(i));
  930. if ((int8_t)idx >= 0) report_error(idx);
  931. }
  932. }
  933. }
  934. else
  935. report_error(I2CPE_idx);
  936. }
  937. #endif // I2C_POSITION_ENCODERS