Implement endstops using new motion control
[clinton/Smoothieware.git] / src / modules / utils / motordrivercontrol / drivers / TMC26X / TMC26X.cpp
CommitLineData
45ef09fd
JM
1/*
2 Highly modifed from....
3
4 TMC26X.cpp - - TMC26X Stepper library for Wiring/Arduino
5
6 based on the stepper library by Tom Igoe, et. al.
7
8 Copyright (c) 2011, Interactive Matter, Marcus Nowotny
9
10 Permission is hereby granted, free of charge, to any person obtaining a copy
11 of this software and associated documentation files (the "Software"), to deal
12 in the Software without restriction, including without limitation the rights
13 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 copies of the Software, and to permit persons to whom the Software is
15 furnished to do so, subject to the following conditions:
16
17 The above copyright notice and this permission notice shall be included in
18 all copies or substantial portions of the Software.
19
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 THE SOFTWARE.
27
28 */
29
30#include "TMC26X.h"
31#include "mbed.h"
32#include "StreamOutput.h"
fc320ac5
JM
33#include "Kernel.h"
34#include "libs/StreamOutputPool.h"
eab91f11
JM
35#include "Robot.h"
36#include "StepperMotor.h"
7daea248
JM
37#include "ConfigValue.h"
38#include "Config.h"
39#include "checksumm.h"
a3bb687b 40#include "StepTicker.h"
7daea248
JM
41
42#define motor_driver_control_checksum CHECKSUM("motor_driver_control")
43#define sense_resistor_checksum CHECKSUM("sense_resistor")
33483c6f
JM
44
45//! return value for TMC26X.getOverTemperature() if there is a overtemperature situation in the TMC chip
46/*!
47 * This warning indicates that the TCM chip is too warm.
48 * It is still working but some parameters may be inferior.
49 * You should do something against it.
50 */
51#define TMC26X_OVERTEMPERATURE_PREWARING 1
52//! return value for TMC26X.getOverTemperature() if there is a overtemperature shutdown in the TMC chip
53/*!
54 * This warning indicates that the TCM chip is too warm to operate and has shut down to prevent damage.
55 * It will stop working until it cools down again.
56 * If you encouter this situation you must do something against it. Like reducing the current or improving the PCB layout
57 * and/or heat management.
58 */
59#define TMC26X_OVERTEMPERATURE_SHUTDOWN 2
60
61//which values can be read out
62/*!
63 * Selects to readout the microstep position from the motor.
64 *\sa readStatus()
65 */
66#define TMC26X_READOUT_POSITION 0
67/*!
68 * Selects to read out the StallGuard value of the motor.
69 *\sa readStatus()
70 */
71#define TMC26X_READOUT_STALLGUARD 1
72/*!
73 * Selects to read out the current current setting (acc. to CoolStep) and the upper bits of the StallGuard value from the motor.
74 *\sa readStatus(), setCurrent()
75 */
76#define TMC26X_READOUT_CURRENT 3
77
78/*!
79 * Define to set the minimum current for CoolStep operation to 1/2 of the selected CS minium.
80 *\sa setCoolStepConfiguration()
81 */
82#define COOL_STEP_HALF_CS_LIMIT 0
83/*!
84 * Define to set the minimum current for CoolStep operation to 1/4 of the selected CS minium.
85 *\sa setCoolStepConfiguration()
86 */
87#define COOL_STEP_QUARTDER_CS_LIMIT 1
88
89
45ef09fd
JM
90//some default values used in initialization
91#define DEFAULT_MICROSTEPPING_VALUE 32
92
93//TMC26X register definitions
33483c6f
JM
94#define DRIVER_CONTROL_REGISTER 0x00000ul
95#define CHOPPER_CONFIG_REGISTER 0x80000ul
96#define COOL_STEP_REGISTER 0xA0000ul
45ef09fd 97#define STALL_GUARD2_LOAD_MEASURE_REGISTER 0xC0000ul
33483c6f
JM
98#define DRIVER_CONFIG_REGISTER 0xE0000ul
99
100#define REGISTER_BIT_PATTERN 0xFFFFFul
45ef09fd 101
33483c6f 102//definitions for the driver control register DRVCTL
6e4b6254
JM
103#define MICROSTEPPING_PATTERN 0x000Ful
104#define STEP_INTERPOLATION 0x0200ul
105#define DOUBLE_EDGE_STEP 0x0100ul
45ef09fd 106
33483c6f 107//definitions for the driver config register DRVCONF
6e4b6254 108#define READ_MICROSTEP_POSITION 0x0000ul
33483c6f
JM
109#define READ_STALL_GUARD_READING 0x0010ul
110#define READ_STALL_GUARD_AND_COOL_STEP 0x0020ul
111#define READ_SELECTION_PATTERN 0x0030ul
112#define VSENSE 0x0040ul
45ef09fd
JM
113
114//definitions for the chopper config register
33483c6f
JM
115#define CHOPPER_MODE_STANDARD 0x00000ul
116#define CHOPPER_MODE_T_OFF_FAST_DECAY 0x04000ul
117#define T_OFF_PATTERN 0x0000ful
118#define RANDOM_TOFF_TIME 0x02000ul
119#define BLANK_TIMING_PATTERN 0x18000ul
120#define BLANK_TIMING_SHIFT 15
121#define HYSTERESIS_DECREMENT_PATTERN 0x01800ul
122#define HYSTERESIS_DECREMENT_SHIFT 11
123#define HYSTERESIS_LOW_VALUE_PATTERN 0x00780ul
124#define HYSTERESIS_LOW_SHIFT 7
125#define HYSTERESIS_START_VALUE_PATTERN 0x00078ul
126#define HYSTERESIS_START_VALUE_SHIFT 4
127#define T_OFF_TIMING_PATERN 0x0000Ful
45ef09fd
JM
128
129//definitions for cool step register
33483c6f 130#define MINIMUM_CURRENT_FOURTH 0x8000ul
45ef09fd 131#define CURRENT_DOWN_STEP_SPEED_PATTERN 0x6000ul
33483c6f
JM
132#define SE_MAX_PATTERN 0x0F00ul
133#define SE_CURRENT_STEP_WIDTH_PATTERN 0x0060ul
134#define SE_MIN_PATTERN 0x000Ful
45ef09fd
JM
135
136//definitions for stall guard2 current register
33483c6f 137#define STALL_GUARD_FILTER_ENABLED 0x10000ul
45ef09fd 138#define STALL_GUARD_TRESHHOLD_VALUE_PATTERN 0x17F00ul
33483c6f
JM
139#define CURRENT_SCALING_PATTERN 0x0001Ful
140#define STALL_GUARD_CONFIG_PATTERN 0x17F00ul
141#define STALL_GUARD_VALUE_PATTERN 0x07F00ul
45ef09fd
JM
142
143//definitions for the input from the TCM260
33483c6f
JM
144#define STATUS_STALL_GUARD_STATUS 0x00001ul
145#define STATUS_OVER_TEMPERATURE_SHUTDOWN 0x00002ul
146#define STATUS_OVER_TEMPERATURE_WARNING 0x00004ul
147#define STATUS_SHORT_TO_GROUND_A 0x00008ul
148#define STATUS_SHORT_TO_GROUND_B 0x00010ul
149#define STATUS_OPEN_LOAD_A 0x00020ul
150#define STATUS_OPEN_LOAD_B 0x00040ul
151#define STATUS_STAND_STILL 0x00080ul
152#define READOUT_VALUE_PATTERN 0xFFC00ul
45ef09fd
JM
153
154//debuging output
155//#define DEBUG
156
157/*
158 * Constructor
159 */
63b7731d 160TMC26X::TMC26X(std::function<int(uint8_t *b, int cnt, uint8_t *r)> spi, char d) : spi(spi), designator(d)
45ef09fd
JM
161{
162 //we are not started yet
163 started = false;
164 //by default cool step is not enabled
165 cool_step_enabled = false;
d74b7399 166 error_reported.reset();
45ef09fd
JM
167}
168
45ef09fd
JM
169/*
170 * configure the stepper driver
171 * just must be called.
172 */
7daea248 173void TMC26X::init(uint16_t cs)
45ef09fd 174{
7daea248
JM
175 // read chip specific config entries
176 this->resistor= THEKERNEL->config->value(motor_driver_control_checksum, cs, sense_resistor_checksum)->by_default(50)->as_number(); // in milliohms
177
45ef09fd 178 //setting the default register values
33483c6f 179 driver_control_register_value = DRIVER_CONTROL_REGISTER;
45ef09fd
JM
180 chopper_config_register = CHOPPER_CONFIG_REGISTER;
181 cool_step_register_value = COOL_STEP_REGISTER;
182 stall_guard2_current_register_value = STALL_GUARD2_LOAD_MEASURE_REGISTER;
183 driver_configuration_register_value = DRIVER_CONFIG_REGISTER | READ_STALL_GUARD_READING;
184
185 //set the initial values
186 send262(driver_control_register_value);
187 send262(chopper_config_register);
188 send262(cool_step_register_value);
189 send262(stall_guard2_current_register_value);
190 send262(driver_configuration_register_value);
191
45ef09fd
JM
192 started = true;
193
d74b7399 194#if 1
45ef09fd
JM
195 //set to a conservative start value
196 setConstantOffTimeChopper(7, 54, 13, 12, 1);
b01faaff
JM
197#else
198 // for 1.5amp kysan @ 12v
eab91f11 199 setSpreadCycleChopper(5, 54, 5, 0, 0);
b01faaff 200 // for 4amp Nema24 @ 12v
df785155 201 //setSpreadCycleChopper(5, 54, 4, 0, 0);
b01faaff 202#endif
45ef09fd 203
b023850b 204 setEnabled(false);
85a27d82 205
45ef09fd
JM
206 //set a nice microstepping value
207 setMicrosteps(DEFAULT_MICROSTEPPING_VALUE);
85a27d82
JM
208
209 // set stallguard to a conservative value so it doesn't trigger immediately
210 setStallGuardThreshold(10, 1);
45ef09fd
JM
211}
212
213void TMC26X::setCurrent(unsigned int current)
214{
215 uint8_t current_scaling = 0;
216 //calculate the current scaling from the max current setting (in mA)
217 double mASetting = (double)current;
218 double resistor_value = (double) this->resistor;
219 // remove vesense flag
220 this->driver_configuration_register_value &= ~(VSENSE);
221 //this is derrived from I=(cs+1)/32*(Vsense/Rsense)
222 //leading to cs = CS = 32*R*I/V (with V = 0,31V oder 0,165V and I = 1000*current)
223 //with Rsense=0,15
224 //for vsense = 0,310V (VSENSE not set)
225 //or vsense = 0,165V (VSENSE set)
fc320ac5 226 current_scaling = (uint8_t)((resistor_value * mASetting * 32.0F / (0.31F * 1000.0F * 1000.0F)) - 0.5F); //theoretically - 1.0 for better rounding it is 0.5
45ef09fd 227
fc320ac5 228 //check if the current scaling is too low
45ef09fd
JM
229 if (current_scaling < 16) {
230 //set the csense bit to get a use half the sense voltage (to support lower motor currents)
231 this->driver_configuration_register_value |= VSENSE;
232 //and recalculate the current setting
fc320ac5 233 current_scaling = (uint8_t)((resistor_value * mASetting * 32.0F / (0.165F * 1000.0F * 1000.0F)) - 0.5F); //theoretically - 1.0 for better rounding it is 0.5
45ef09fd
JM
234 }
235
236 //do some sanity checks
237 if (current_scaling > 31) {
238 current_scaling = 31;
239 }
240 //delete the old value
241 stall_guard2_current_register_value &= ~(CURRENT_SCALING_PATTERN);
242 //set the new current scaling
243 stall_guard2_current_register_value |= current_scaling;
244 //if started we directly send it to the motor
245 if (started) {
246 send262(driver_configuration_register_value);
247 send262(stall_guard2_current_register_value);
248 }
249}
250
251unsigned int TMC26X::getCurrent(void)
252{
253 //we calculate the current according to the datasheet to be on the safe side
254 //this is not the fastest but the most accurate and illustrative way
255 double result = (double)(stall_guard2_current_register_value & CURRENT_SCALING_PATTERN);
256 double resistor_value = (double)this->resistor;
fc320ac5
JM
257 double voltage = (driver_configuration_register_value & VSENSE) ? 0.165F : 0.31F;
258 result = (result + 1.0F) / 32.0F * voltage / resistor_value * 1000.0F * 1000.0F;
45ef09fd
JM
259 return (unsigned int)result;
260}
261
262void TMC26X::setStallGuardThreshold(int8_t stall_guard_threshold, int8_t stall_guard_filter_enabled)
263{
264 if (stall_guard_threshold < -64) {
265 stall_guard_threshold = -64;
266 //We just have 5 bits
267 } else if (stall_guard_threshold > 63) {
268 stall_guard_threshold = 63;
269 }
270 //add trim down to 7 bits
271 stall_guard_threshold &= 0x7f;
272 //delete old stall guard settings
273 stall_guard2_current_register_value &= ~(STALL_GUARD_CONFIG_PATTERN);
274 if (stall_guard_filter_enabled) {
275 stall_guard2_current_register_value |= STALL_GUARD_FILTER_ENABLED;
276 }
277 //Set the new stall guard threshold
278 stall_guard2_current_register_value |= (((unsigned long)stall_guard_threshold << 8) & STALL_GUARD_CONFIG_PATTERN);
279 //if started we directly send it to the motor
280 if (started) {
281 send262(stall_guard2_current_register_value);
282 }
283}
284
285int8_t TMC26X::getStallGuardThreshold(void)
286{
287 unsigned long stall_guard_threshold = stall_guard2_current_register_value & STALL_GUARD_VALUE_PATTERN;
288 //shift it down to bit 0
289 stall_guard_threshold >>= 8;
290 //convert the value to an int to correctly handle the negative numbers
291 int8_t result = stall_guard_threshold;
292 //check if it is negative and fill it up with leading 1 for proper negative number representation
df16e9b0 293 if (result & (1 << 6)) {
45ef09fd
JM
294 result |= 0xC0;
295 }
296 return result;
297}
298
299int8_t TMC26X::getStallGuardFilter(void)
300{
301 if (stall_guard2_current_register_value & STALL_GUARD_FILTER_ENABLED) {
302 return -1;
303 } else {
304 return 0;
305 }
306}
307/*
308 * Set the number of microsteps per step.
309 * 0,2,4,8,16,32,64,128,256 is supported
310 * any value in between will be mapped to the next smaller value
311 * 0 and 1 set the motor in full step mode
312 */
313void TMC26X::setMicrosteps(int number_of_steps)
314{
315 long setting_pattern;
316 //poor mans log
317 if (number_of_steps >= 256) {
318 setting_pattern = 0;
319 microsteps = 256;
320 } else if (number_of_steps >= 128) {
321 setting_pattern = 1;
322 microsteps = 128;
323 } else if (number_of_steps >= 64) {
324 setting_pattern = 2;
325 microsteps = 64;
326 } else if (number_of_steps >= 32) {
327 setting_pattern = 3;
328 microsteps = 32;
329 } else if (number_of_steps >= 16) {
330 setting_pattern = 4;
331 microsteps = 16;
332 } else if (number_of_steps >= 8) {
333 setting_pattern = 5;
334 microsteps = 8;
335 } else if (number_of_steps >= 4) {
336 setting_pattern = 6;
337 microsteps = 4;
338 } else if (number_of_steps >= 2) {
339 setting_pattern = 7;
340 microsteps = 2;
341 //1 and 0 lead to full step
342 } else if (number_of_steps <= 1) {
343 setting_pattern = 8;
344 microsteps = 1;
345 }
346
347 //delete the old value
348 this->driver_control_register_value &= 0xFFFF0ul;
349 //set the new value
350 this->driver_control_register_value |= setting_pattern;
351
352 //if started we directly send it to the motor
353 if (started) {
354 send262(driver_control_register_value);
355 }
356}
357
358/*
359 * returns the effective number of microsteps at the moment
360 */
361int TMC26X::getMicrosteps(void)
362{
363 return microsteps;
364}
365
33483c6f
JM
366void TMC26X::setStepInterpolation(int8_t value)
367{
368 if (value) {
369 driver_control_register_value |= STEP_INTERPOLATION;
370 } else {
371 driver_control_register_value &= ~(STEP_INTERPOLATION);
372 }
373 //if started we directly send it to the motor
374 if (started) {
375 send262(driver_control_register_value);
376 }
377}
378
379void TMC26X::setDoubleEdge(int8_t value)
380{
381 if (value) {
382 driver_control_register_value |= DOUBLE_EDGE_STEP;
383 } else {
384 driver_control_register_value &= ~(DOUBLE_EDGE_STEP);
385 }
386 //if started we directly send it to the motor
387 if (started) {
388 send262(driver_control_register_value);
389 }
390}
391
45ef09fd
JM
392/*
393 * constant_off_time: The off time setting controls the minimum chopper frequency.
394 * For most applications an off time within the range of 5μs to 20μs will fit.
395 * 2...15: off time setting
396 *
397 * blank_time: Selects the comparator blank time. This time needs to safely cover the switching event and the
398 * duration of the ringing on the sense resistor. For
399 * 0: min. setting 3: max. setting
400 *
401 * fast_decay_time_setting: Fast decay time setting. With CHM=1, these bits control the portion of fast decay for each chopper cycle.
402 * 0: slow decay only
403 * 1...15: duration of fast decay phase
404 *
405 * sine_wave_offset: Sine wave offset. With CHM=1, these bits control the sine wave offset.
406 * A positive offset corrects for zero crossing error.
407 * -3..-1: negative offset 0: no offset 1...12: positive offset
408 *
409 * use_current_comparator: Selects usage of the current comparator for termination of the fast decay cycle.
410 * If current comparator is enabled, it terminates the fast decay cycle in case the current
411 * reaches a higher negative value than the actual positive value.
412 * 1: enable comparator termination of fast decay cycle
413 * 0: end by time only
414 */
415void TMC26X::setConstantOffTimeChopper(int8_t constant_off_time, int8_t blank_time, int8_t fast_decay_time_setting, int8_t sine_wave_offset, uint8_t use_current_comparator)
416{
417 //perform some sanity checks
418 if (constant_off_time < 2) {
419 constant_off_time = 2;
420 } else if (constant_off_time > 15) {
421 constant_off_time = 15;
422 }
423 //save the constant off time
424 this->constant_off_time = constant_off_time;
425 int8_t blank_value;
426 //calculate the value acc to the clock cycles
427 if (blank_time >= 54) {
428 blank_value = 3;
429 } else if (blank_time >= 36) {
430 blank_value = 2;
431 } else if (blank_time >= 24) {
432 blank_value = 1;
433 } else {
434 blank_value = 0;
435 }
eab91f11
JM
436 this->blank_time = blank_time;
437
45ef09fd
JM
438 if (fast_decay_time_setting < 0) {
439 fast_decay_time_setting = 0;
440 } else if (fast_decay_time_setting > 15) {
441 fast_decay_time_setting = 15;
442 }
443 if (sine_wave_offset < -3) {
444 sine_wave_offset = -3;
445 } else if (sine_wave_offset > 12) {
446 sine_wave_offset = 12;
447 }
448 //shift the sine_wave_offset
449 sine_wave_offset += 3;
450
451 //calculate the register setting
452 //first of all delete all the values for this
453 chopper_config_register &= ~((1 << 12) | BLANK_TIMING_PATTERN | HYSTERESIS_DECREMENT_PATTERN | HYSTERESIS_LOW_VALUE_PATTERN | HYSTERESIS_START_VALUE_PATTERN | T_OFF_TIMING_PATERN);
454 //set the constant off pattern
455 chopper_config_register |= CHOPPER_MODE_T_OFF_FAST_DECAY;
456 //set the blank timing value
457 chopper_config_register |= ((unsigned long)blank_value) << BLANK_TIMING_SHIFT;
458 //setting the constant off time
459 chopper_config_register |= constant_off_time;
460 //set the fast decay time
461 //set msb
462 chopper_config_register |= (((unsigned long)(fast_decay_time_setting & 0x8)) << HYSTERESIS_DECREMENT_SHIFT);
463 //other bits
464 chopper_config_register |= (((unsigned long)(fast_decay_time_setting & 0x7)) << HYSTERESIS_START_VALUE_SHIFT);
465 //set the sine wave offset
466 chopper_config_register |= (unsigned long)sine_wave_offset << HYSTERESIS_LOW_SHIFT;
467 //using the current comparator?
468 if (!use_current_comparator) {
469 chopper_config_register |= (1 << 12);
470 }
471 //if started we directly send it to the motor
472 if (started) {
473 send262(chopper_config_register);
474 }
475}
476
477/*
478 * constant_off_time: The off time setting controls the minimum chopper frequency.
479 * For most applications an off time within the range of 5μs to 20μs will fit.
480 * 2...15: off time setting
481 *
482 * blank_time: Selects the comparator blank time. This time needs to safely cover the switching event and the
483 * duration of the ringing on the sense resistor. For
484 * 0: min. setting 3: max. setting
485 *
486 * hysteresis_start: Hysteresis start setting. Please remark, that this value is an offset to the hysteresis end value HEND.
487 * 1...8
488 *
489 * hysteresis_end: Hysteresis end setting. Sets the hysteresis end value after a number of decrements. Decrement interval time is controlled by HDEC.
490 * The sum HSTRT+HEND must be <16. At a current setting CS of max. 30 (amplitude reduced to 240), the sum is not limited.
491 * -3..-1: negative HEND 0: zero HEND 1...12: positive HEND
492 *
493 * hysteresis_decrement: Hysteresis decrement setting. This setting determines the slope of the hysteresis during on time and during fast decay time.
494 * 0: fast decrement 3: very slow decrement
495 */
496
497void TMC26X::setSpreadCycleChopper(int8_t constant_off_time, int8_t blank_time, int8_t hysteresis_start, int8_t hysteresis_end, int8_t hysteresis_decrement)
498{
eab91f11
JM
499 h_start = hysteresis_start;
500 h_end = hysteresis_end;
501 h_decrement = hysteresis_decrement;
502 this->blank_time = blank_time;
503
45ef09fd
JM
504 //perform some sanity checks
505 if (constant_off_time < 2) {
506 constant_off_time = 2;
507 } else if (constant_off_time > 15) {
508 constant_off_time = 15;
509 }
510 //save the constant off time
511 this->constant_off_time = constant_off_time;
512 int8_t blank_value;
513 //calculate the value acc to the clock cycles
514 if (blank_time >= 54) {
515 blank_value = 3;
516 } else if (blank_time >= 36) {
517 blank_value = 2;
518 } else if (blank_time >= 24) {
519 blank_value = 1;
520 } else {
521 blank_value = 0;
522 }
eab91f11 523
45ef09fd
JM
524 if (hysteresis_start < 1) {
525 hysteresis_start = 1;
526 } else if (hysteresis_start > 8) {
527 hysteresis_start = 8;
528 }
529 hysteresis_start--;
530
531 if (hysteresis_end < -3) {
532 hysteresis_end = -3;
533 } else if (hysteresis_end > 12) {
534 hysteresis_end = 12;
535 }
536 //shift the hysteresis_end
537 hysteresis_end += 3;
538
539 if (hysteresis_decrement < 0) {
540 hysteresis_decrement = 0;
541 } else if (hysteresis_decrement > 3) {
542 hysteresis_decrement = 3;
543 }
544
545 //first of all delete all the values for this
546 chopper_config_register &= ~(CHOPPER_MODE_T_OFF_FAST_DECAY | BLANK_TIMING_PATTERN | HYSTERESIS_DECREMENT_PATTERN | HYSTERESIS_LOW_VALUE_PATTERN | HYSTERESIS_START_VALUE_PATTERN | T_OFF_TIMING_PATERN);
547
548 //set the blank timing value
549 chopper_config_register |= ((unsigned long)blank_value) << BLANK_TIMING_SHIFT;
550 //setting the constant off time
551 chopper_config_register |= constant_off_time;
552 //set the hysteresis_start
553 chopper_config_register |= ((unsigned long)hysteresis_start) << HYSTERESIS_START_VALUE_SHIFT;
554 //set the hysteresis end
555 chopper_config_register |= ((unsigned long)hysteresis_end) << HYSTERESIS_LOW_SHIFT;
556 //set the hystereis decrement
b01faaff 557 chopper_config_register |= ((unsigned long)hysteresis_decrement) << HYSTERESIS_DECREMENT_SHIFT;
eab91f11 558
45ef09fd
JM
559 //if started we directly send it to the motor
560 if (started) {
561 send262(chopper_config_register);
562 }
563}
564
565/*
566 * In a constant off time chopper scheme both coil choppers run freely, i.e. are not synchronized.
567 * The frequency of each chopper mainly depends on the coil current and the position dependant motor coil inductivity, thus it depends on the microstep position.
568 * With some motors a slightly audible beat can occur between the chopper frequencies, especially when they are near to each other. This typically occurs at a
569 * few microstep positions within each quarter wave. This effect normally is not audible when compared to mechanical noise generated by ball bearings, etc.
570 * Further factors which can cause a similar effect are a poor layout of sense resistor GND connection.
571 * Hint: A common factor, which can cause motor noise, is a bad PCB layout causing coupling of both sense resistor voltages
572 * (please refer to sense resistor layout hint in chapter 8.1).
573 * In order to minimize the effect of a beat between both chopper frequencies, an internal random generator is provided.
574 * It modulates the slow decay time setting when switched on by the RNDTF bit. The RNDTF feature further spreads the chopper spectrum,
575 * reducing electromagnetic emission on single frequencies.
576 */
577void TMC26X::setRandomOffTime(int8_t value)
578{
579 if (value) {
580 chopper_config_register |= RANDOM_TOFF_TIME;
581 } else {
582 chopper_config_register &= ~(RANDOM_TOFF_TIME);
583 }
584 //if started we directly send it to the motor
585 if (started) {
586 send262(chopper_config_register);
587 }
588}
589
590void TMC26X::setCoolStepConfiguration(unsigned int lower_SG_threshold, unsigned int SG_hysteresis, uint8_t current_decrement_step_size,
df16e9b0 591 uint8_t current_increment_step_size, uint8_t lower_current_limit)
45ef09fd
JM
592{
593 //sanitize the input values
594 if (lower_SG_threshold > 480) {
595 lower_SG_threshold = 480;
596 }
597 //divide by 32
598 lower_SG_threshold >>= 5;
599 if (SG_hysteresis > 480) {
600 SG_hysteresis = 480;
601 }
602 //divide by 32
603 SG_hysteresis >>= 5;
604 if (current_decrement_step_size > 3) {
605 current_decrement_step_size = 3;
606 }
607 if (current_increment_step_size > 3) {
608 current_increment_step_size = 3;
609 }
610 if (lower_current_limit > 1) {
611 lower_current_limit = 1;
612 }
613 //store the lower level in order to enable/disable the cool step
614 this->cool_step_lower_threshold = lower_SG_threshold;
615 //if cool step is not enabled we delete the lower value to keep it disabled
616 if (!this->cool_step_enabled) {
617 lower_SG_threshold = 0;
618 }
619 //the good news is that we can start with a complete new cool step register value
620 //and simply set the values in the register
621 cool_step_register_value = ((unsigned long)lower_SG_threshold) | (((unsigned long)SG_hysteresis) << 8) | (((unsigned long)current_decrement_step_size) << 5)
622 | (((unsigned long)current_increment_step_size) << 13) | (((unsigned long)lower_current_limit) << 15)
623 //and of course we have to include the signature of the register
624 | COOL_STEP_REGISTER;
625
626 if (started) {
627 send262(cool_step_register_value);
628 }
629}
630
631void TMC26X::setCoolStepEnabled(bool enabled)
632{
633 //simply delete the lower limit to disable the cool step
634 cool_step_register_value &= ~SE_MIN_PATTERN;
635 //and set it to the proper value if cool step is to be enabled
636 if (enabled) {
637 cool_step_register_value |= this->cool_step_lower_threshold;
638 }
639 //and save the enabled status
640 this->cool_step_enabled = enabled;
641 //save the register value
642 if (started) {
643 send262(cool_step_register_value);
644 }
645}
646
647bool TMC26X::isCoolStepEnabled(void)
648{
649 return this->cool_step_enabled;
650}
651
652unsigned int TMC26X::getCoolStepLowerSgThreshold()
653{
654 //we return our internally stored value - in order to provide the correct setting even if cool step is not enabled
655 return this->cool_step_lower_threshold << 5;
656}
657
658unsigned int TMC26X::getCoolStepUpperSgThreshold()
659{
660 return (uint8_t)((cool_step_register_value & SE_MAX_PATTERN) >> 8) << 5;
661}
662
663uint8_t TMC26X::getCoolStepCurrentIncrementSize()
664{
665 return (uint8_t)((cool_step_register_value & CURRENT_DOWN_STEP_SPEED_PATTERN) >> 13);
666}
667
668uint8_t TMC26X::getCoolStepNumberOfSGReadings()
669{
670 return (uint8_t)((cool_step_register_value & SE_CURRENT_STEP_WIDTH_PATTERN) >> 5);
671}
672
673uint8_t TMC26X::getCoolStepLowerCurrentLimit()
674{
675 return (uint8_t)((cool_step_register_value & MINIMUM_CURRENT_FOURTH) >> 15);
676}
677
678void TMC26X::setEnabled(bool enabled)
679{
680 //delete the t_off in the chopper config to get sure
681 chopper_config_register &= ~(T_OFF_PATTERN);
682 if (enabled) {
683 //and set the t_off time
684 chopper_config_register |= this->constant_off_time;
685 }
686 //if not enabled we don't have to do anything since we already delete t_off from the register
687 if (started) {
688 send262(chopper_config_register);
689 }
690}
691
692bool TMC26X::isEnabled()
693{
694 if (chopper_config_register & T_OFF_PATTERN) {
695 return true;
696 } else {
697 return false;
698 }
699}
700
701/*
702 * reads a value from the TMC26X status register. The value is not obtained directly but can then
703 * be read by the various status routines.
704 *
705 */
706void TMC26X::readStatus(int8_t read_value)
707{
708 unsigned long old_driver_configuration_register_value = driver_configuration_register_value;
709 //reset the readout configuration
710 driver_configuration_register_value &= ~(READ_SELECTION_PATTERN);
711 //this now equals TMC26X_READOUT_POSITION - so we just have to check the other two options
712 if (read_value == TMC26X_READOUT_STALLGUARD) {
713 driver_configuration_register_value |= READ_STALL_GUARD_READING;
714 } else if (read_value == TMC26X_READOUT_CURRENT) {
715 driver_configuration_register_value |= READ_STALL_GUARD_AND_COOL_STEP;
716 }
717 //all other cases are ignored to prevent funny values
718 //check if the readout is configured for the value we are interested in
719 if (driver_configuration_register_value != old_driver_configuration_register_value) {
720 //because then we need to write the value twice - one time for configuring, second time to get the value, see below
721 send262(driver_configuration_register_value);
722 }
723 //write the configuration to get the last status
724 send262(driver_configuration_register_value);
725}
726
727//reads the stall guard setting from last status
728//returns -1 if stallguard information is not present
729int TMC26X::getCurrentStallGuardReading(void)
730{
731 //if we don't yet started there cannot be a stall guard value
732 if (!started) {
733 return -1;
734 }
735 //not time optimal, but solution optiomal:
736 //first read out the stall guard value
737 readStatus(TMC26X_READOUT_STALLGUARD);
738 return getReadoutValue();
739}
740
741uint8_t TMC26X::getCurrentCSReading(void)
742{
743 //if we don't yet started there cannot be a stall guard value
744 if (!started) {
745 return 0;
746 }
7b35a4c8 747
45ef09fd
JM
748 //first read out the stall guard value
749 readStatus(TMC26X_READOUT_CURRENT);
750 return (getReadoutValue() & 0x1f);
751}
752
33483c6f 753unsigned int TMC26X::getCoolstepCurrent(void)
45ef09fd 754{
7b35a4c8
JM
755 float result = (float)getCurrentCSReading();
756 float resistor_value = (float)this->resistor;
757 float voltage = (driver_configuration_register_value & VSENSE) ? 0.165F : 0.31F;
758 result = (result + 1.0F) / 32.0F * voltage / resistor_value * 1000.0F * 1000.0F;
759 return (unsigned int)roundf(result);
45ef09fd
JM
760}
761
762/*
763 return true if the stallguard threshold has been reached
764*/
765bool TMC26X::isStallGuardOverThreshold(void)
766{
767 if (!this->started) {
768 return false;
769 }
770 return (driver_status_result & STATUS_STALL_GUARD_STATUS);
771}
772
773/*
774 returns if there is any over temperature condition:
775 OVER_TEMPERATURE_PREWARING if pre warning level has been reached
776 OVER_TEMPERATURE_SHUTDOWN if the temperature is so hot that the driver is shut down
777 Any of those levels are not too good.
778*/
779int8_t TMC26X::getOverTemperature(void)
780{
781 if (!this->started) {
782 return 0;
783 }
784 if (driver_status_result & STATUS_OVER_TEMPERATURE_SHUTDOWN) {
785 return TMC26X_OVERTEMPERATURE_SHUTDOWN;
786 }
787 if (driver_status_result & STATUS_OVER_TEMPERATURE_WARNING) {
788 return TMC26X_OVERTEMPERATURE_PREWARING;
789 }
790 return 0;
791}
792
793//is motor channel A shorted to ground
794bool TMC26X::isShortToGroundA(void)
795{
796 if (!this->started) {
797 return false;
798 }
799 return (driver_status_result & STATUS_SHORT_TO_GROUND_A);
800}
801
802//is motor channel B shorted to ground
803bool TMC26X::isShortToGroundB(void)
804{
805 if (!this->started) {
806 return false;
807 }
808 return (driver_status_result & STATUS_SHORT_TO_GROUND_B);
809}
810
811//is motor channel A connected
812bool TMC26X::isOpenLoadA(void)
813{
814 if (!this->started) {
815 return false;
816 }
817 return (driver_status_result & STATUS_OPEN_LOAD_A);
818}
819
820//is motor channel B connected
821bool TMC26X::isOpenLoadB(void)
822{
823 if (!this->started) {
824 return false;
825 }
826 return (driver_status_result & STATUS_OPEN_LOAD_B);
827}
828
829//is chopper inactive since 2^20 clock cycles - defaults to ~0,08s
830bool TMC26X::isStandStill(void)
831{
832 if (!this->started) {
833 return false;
834 }
835 return (driver_status_result & STATUS_STAND_STILL);
836}
837
838//is chopper inactive since 2^20 clock cycles - defaults to ~0,08s
839bool TMC26X::isStallGuardReached(void)
840{
841 if (!this->started) {
842 return false;
843 }
844 return (driver_status_result & STATUS_STALL_GUARD_STATUS);
845}
846
847//reads the stall guard setting from last status
848//returns -1 if stallguard inforamtion is not present
849int TMC26X::getReadoutValue(void)
850{
851 return (int)(driver_status_result >> 10);
852}
853
45ef09fd
JM
854bool TMC26X::isCurrentScalingHalfed()
855{
856 if (this->driver_configuration_register_value & VSENSE) {
857 return true;
858 } else {
859 return false;
860 }
861}
45ef09fd 862
eab91f11 863void TMC26X::dumpStatus(StreamOutput *stream, bool readable)
45ef09fd 864{
eab91f11 865 if (readable) {
63b7731d 866 stream->printf("designator %c, Chip type TMC26X\n", designator);
33483c6f 867
5262f8d0 868 check_error_status_bits(stream);
33483c6f 869
45ef09fd
JM
870 if (this->isStallGuardReached()) {
871 stream->printf("INFO: Stall Guard level reached!\n");
872 }
33483c6f 873
45ef09fd
JM
874 if (this->isStandStill()) {
875 stream->printf("INFO: Motor is standing still.\n");
876 }
877
45ef09fd 878 int value = getReadoutValue();
7b35a4c8 879 stream->printf("Microstep postion phase A: %d\n", value);
45ef09fd 880
33483c6f 881 value = getCurrentStallGuardReading();
7b35a4c8 882 stream->printf("Stall Guard value: %d\n", value);
fc320ac5 883
33483c6f
JM
884 stream->printf("Current setting: %dmA\n", getCurrent());
885 stream->printf("Coolstep current: %dmA\n", getCoolstepCurrent());
7b35a4c8 886
fc320ac5
JM
887 stream->printf("Microsteps: 1/%d\n", microsteps);
888
889 stream->printf("Register dump:\n");
7b35a4c8
JM
890 stream->printf(" driver control register: %08lX(%ld)\n", driver_control_register_value, driver_control_register_value);
891 stream->printf(" chopper config register: %08lX(%ld)\n", chopper_config_register, chopper_config_register);
892 stream->printf(" cool step register: %08lX(%ld)\n", cool_step_register_value, cool_step_register_value);
893 stream->printf(" stall guard2 current register: %08lX(%ld)\n", stall_guard2_current_register_value, stall_guard2_current_register_value);
894 stream->printf(" driver configuration register: %08lX(%ld)\n", driver_configuration_register_value, driver_configuration_register_value);
b01faaff 895 stream->printf(" motor_driver_control.xxx.reg %05lX,%05lX,%05lX,%05lX,%05lX\n", driver_control_register_value, chopper_config_register, cool_step_register_value, stall_guard2_current_register_value, driver_configuration_register_value);
eab91f11
JM
896
897 } else {
898 // TODO hardcoded for X need to select ABC as needed
c8bac202 899 bool moving = THEROBOT->actuators[0]->is_moving();
eab91f11
JM
900 // dump out in the format that the processing script needs
901 if (moving) {
c8bac202 902 stream->printf("#sg%d,p%lu,k%u,r,", getCurrentStallGuardReading(), THEROBOT->actuators[0]->get_current_step(), getCoolstepCurrent());
eab91f11
JM
903 } else {
904 readStatus(TMC26X_READOUT_POSITION); // get the status bits
905 stream->printf("#s,");
906 }
c8bac202 907 stream->printf("d%d,", THEROBOT->actuators[0]->which_direction() ? 1 : -1);
eab91f11
JM
908 stream->printf("c%u,m%d,", getCurrent(), getMicrosteps());
909 // stream->printf('S');
910 // stream->printf(tmc26XStepper.getSpeed(), DEC);
911 stream->printf("t%d,f%d,", getStallGuardThreshold(), getStallGuardFilter());
912
913 //print out the general cool step config
914 if (isCoolStepEnabled()) stream->printf("Ke+,");
915 else stream->printf("Ke-,");
916
917 stream->printf("Kl%u,Ku%u,Kn%u,Ki%u,Km%u,",
918 getCoolStepLowerSgThreshold(), getCoolStepUpperSgThreshold(), getCoolStepNumberOfSGReadings(), getCoolStepCurrentIncrementSize(), getCoolStepLowerCurrentLimit());
919
920 //detect the winding status
921 if (isOpenLoadA()) {
922 stream->printf("ao,");
923 } else if(isShortToGroundA()) {
924 stream->printf("ag,");
925 } else {
926 stream->printf("a-,");
927 }
928 //detect the winding status
929 if (isOpenLoadB()) {
930 stream->printf("bo,");
931 } else if(isShortToGroundB()) {
932 stream->printf("bg,");
933 } else {
934 stream->printf("b-,");
935 }
936
937 char temperature = getOverTemperature();
938 if (temperature == 0) {
939 stream->printf("x-,");
940 } else if (temperature == TMC26X_OVERTEMPERATURE_PREWARING) {
941 stream->printf("xw,");
942 } else {
943 stream->printf("xe,");
944 }
945
946 if (isEnabled()) {
947 stream->printf("e1,");
948 } else {
949 stream->printf("e0,");
950 }
951
952 //write out the current chopper config
df785155 953 stream->printf("Cm%d,", (chopper_config_register & CHOPPER_MODE_T_OFF_FAST_DECAY) != 0);
eab91f11
JM
954 stream->printf("Co%d,Cb%d,", constant_off_time, blank_time);
955 if ((chopper_config_register & CHOPPER_MODE_T_OFF_FAST_DECAY) == 0) {
956 stream->printf("Cs%d,Ce%d,Cd%d,", h_start, h_end, h_decrement);
957 }
958 stream->printf("\n");
7b35a4c8
JM
959 }
960}
961
d74b7399 962// check error bits and report, only report once
5262f8d0
JM
963bool TMC26X::check_error_status_bits(StreamOutput *stream)
964{
965 bool error= false;
966 readStatus(TMC26X_READOUT_POSITION); // get the status bits
967
968 if (this->getOverTemperature()&TMC26X_OVERTEMPERATURE_PREWARING) {
63b7731d 969 if(!error_reported.test(0)) stream->printf("%c - WARNING: Overtemperature Prewarning!\n", designator);
d74b7399
JM
970 error_reported.set(0);
971 }else{
972 error_reported.reset(0);
973 }
5262f8d0 974
d74b7399 975 if (this->getOverTemperature()&TMC26X_OVERTEMPERATURE_SHUTDOWN) {
63b7731d 976 if(!error_reported.test(1)) stream->printf("%c - ERROR: Overtemperature Shutdown!\n", designator);
5262f8d0 977 error=true;
d74b7399
JM
978 error_reported.set(1);
979 }else{
980 error_reported.reset(1);
5262f8d0
JM
981 }
982
983 if (this->isShortToGroundA()) {
63b7731d 984 if(!error_reported.test(2)) stream->printf("%c - ERROR: SHORT to ground on channel A!\n", designator);
5262f8d0 985 error=true;
d74b7399
JM
986 error_reported.set(2);
987 }else{
988 error_reported.reset(2);
5262f8d0
JM
989 }
990
991 if (this->isShortToGroundB()) {
63b7731d 992 if(!error_reported.test(3)) stream->printf("%c - ERROR: SHORT to ground on channel B!\n", designator);
5262f8d0 993 error=true;
d74b7399
JM
994 error_reported.set(3);
995 }else{
996 error_reported.reset(3);
5262f8d0
JM
997 }
998
d74b7399 999 // these seem to be triggered when moving so ignore them for now
5262f8d0 1000 if (this->isOpenLoadA()) {
63b7731d 1001 if(!error_reported.test(4)) stream->printf("%c - ERROR: Channel A seems to be unconnected!\n", designator);
5262f8d0 1002 error=true;
d74b7399
JM
1003 error_reported.set(4);
1004 }else{
1005 error_reported.reset(4);
5262f8d0
JM
1006 }
1007
1008 if (this->isOpenLoadB()) {
63b7731d 1009 if(!error_reported.test(5)) stream->printf("%c - ERROR: Channel B seems to be unconnected!\n", designator);
5262f8d0 1010 error=true;
d74b7399
JM
1011 error_reported.set(5);
1012 }else{
1013 error_reported.reset(5);
5262f8d0
JM
1014 }
1015
d74b7399
JM
1016 // if(error) {
1017 // stream->printf("%08X\n", driver_status_result);
1018 // }
5262f8d0
JM
1019 return error;
1020}
1021
1022bool TMC26X::checkAlarm()
1023{
1024 return check_error_status_bits(THEKERNEL->streams);
1025}
1026
7b35a4c8 1027// sets a raw register to the value specified, for advanced settings
30faab4a 1028// register 255 writes them, 0 displays what registers are mapped to what
3c9fee28 1029// FIXME status registers not reading back correctly, check docs
7b35a4c8
JM
1030bool TMC26X::setRawRegister(StreamOutput *stream, uint32_t reg, uint32_t val)
1031{
1032 switch(reg) {
df16e9b0 1033 case 255:
7b35a4c8
JM
1034 send262(driver_control_register_value);
1035 send262(chopper_config_register);
1036 send262(cool_step_register_value);
1037 send262(stall_guard2_current_register_value);
1038 send262(driver_configuration_register_value);
1039 stream->printf("Registers written\n");
1040 break;
1041
df16e9b0 1042
d74b7399
JM
1043 case 1: driver_control_register_value = val; stream->printf("driver control register set to %08lX\n", val); break;
1044 case 2: chopper_config_register = val; stream->printf("chopper config register set to %08lX\n", val); break;
1045 case 3: cool_step_register_value = val; stream->printf("cool step register set to %08lX\n", val); break;
1046 case 4: stall_guard2_current_register_value = val; stream->printf("stall guard2 current register set to %08lX\n", val); break;
1047 case 5: driver_configuration_register_value = val; stream->printf("driver configuration register set to %08lX\n", val); break;
df16e9b0
JM
1048
1049 default:
7b35a4c8
JM
1050 stream->printf("1: driver control register\n");
1051 stream->printf("2: chopper config register\n");
1052 stream->printf("3: cool step register\n");
1053 stream->printf("4: stall guard2 current register\n");
1054 stream->printf("5: driver configuration register\n");
df16e9b0
JM
1055 stream->printf("255: update all registers\n");
1056 return false;
7b35a4c8
JM
1057 }
1058 return true;
45ef09fd
JM
1059}
1060
1061/*
1062 * send register settings to the stepper driver via SPI
1063 * returns the current status
fc320ac5 1064 * sends 20bits, the last 20 bits of the 24bits is taken as the command
45ef09fd
JM
1065 */
1066void TMC26X::send262(unsigned long datagram)
1067{
df16e9b0 1068 uint8_t buf[] {(uint8_t)(datagram >> 16), (uint8_t)(datagram >> 8), (uint8_t)(datagram & 0xff)};
45ef09fd
JM
1069 uint8_t rbuf[3];
1070
45ef09fd
JM
1071 //write/read the values
1072 spi(buf, 3, rbuf);
1073
1074 // construct reply
1075 unsigned long i_datagram = ((rbuf[0] << 16) | (rbuf[1] << 8) | (rbuf[2])) >> 4;
1076
1077 //store the datagram as status result
1078 driver_status_result = i_datagram;
fc320ac5 1079
7b35a4c8 1080 //THEKERNEL->streams->printf("sent: %02X, %02X, %02X received: %02X, %02X, %02X \n", buf[0], buf[1], buf[2], rbuf[0], rbuf[1], rbuf[2]);
45ef09fd 1081}
df16e9b0 1082
ca7b6b44
JM
1083#define HAS(X) (options.find(X) != options.end())
1084#define GET(X) (options.at(X))
df16e9b0
JM
1085bool TMC26X::set_options(const options_t& options)
1086{
eab91f11 1087 bool set = false;
b023850b 1088 if(HAS('O') || HAS('Q')) {
df16e9b0 1089 // void TMC26X::setStallGuardThreshold(int8_t stall_guard_threshold, int8_t stall_guard_filter_enabled)
eab91f11
JM
1090 int8_t o = HAS('O') ? GET('O') : getStallGuardThreshold();
1091 int8_t q = HAS('Q') ? GET('Q') : getStallGuardFilter();
b023850b 1092 setStallGuardThreshold(o, q);
eab91f11 1093 set = true;
df16e9b0
JM
1094 }
1095
ca7b6b44
JM
1096 if(HAS('H') && HAS('I') && HAS('J') && HAS('K') && HAS('L')) {
1097 //void TMC26X::setCoolStepConfiguration(unsigned int lower_SG_threshold, unsigned int SG_hysteresis, uint8_t current_decrement_step_size, uint8_t current_increment_step_size, uint8_t lower_current_limit)
1098 setCoolStepConfiguration(GET('H'), GET('I'), GET('J'), GET('K'), GET('L'));
eab91f11 1099 set = true;
ca7b6b44
JM
1100 }
1101
1102 if(HAS('S')) {
eab91f11
JM
1103 uint32_t s = GET('S');
1104 if(s == 0 && HAS('U') && HAS('V') && HAS('W') && HAS('X') && HAS('Y')) {
6e4b6254 1105 //void TMC26X::setConstantOffTimeChopper(int8_t constant_off_time, int8_t blank_time, int8_t fast_decay_time_setting, int8_t sine_wave_offset, uint8_t use_current_comparator)
ca7b6b44 1106 setConstantOffTimeChopper(GET('U'), GET('V'), GET('W'), GET('X'), GET('Y'));
eab91f11 1107 set = true;
df16e9b0 1108
eab91f11 1109 } else if(s == 1 && HAS('U') && HAS('V') && HAS('W') && HAS('X') && HAS('Y')) {
6e4b6254 1110 //void TMC26X::setSpreadCycleChopper(int8_t constant_off_time, int8_t blank_time, int8_t hysteresis_start, int8_t hysteresis_end, int8_t hysteresis_decrement);
ca7b6b44 1111 setSpreadCycleChopper(GET('U'), GET('V'), GET('W'), GET('X'), GET('Y'));
eab91f11 1112 set = true;
df16e9b0 1113
eab91f11 1114 } else if(s == 2 && HAS('Z')) {
ca7b6b44 1115 setRandomOffTime(GET('Z'));
eab91f11 1116 set = true;
6e4b6254 1117
eab91f11 1118 } else if(s == 3 && HAS('Z')) {
ca7b6b44 1119 setDoubleEdge(GET('Z'));
eab91f11 1120 set = true;
6e4b6254 1121
eab91f11 1122 } else if(s == 4 && HAS('Z')) {
ca7b6b44 1123 setStepInterpolation(GET('Z'));
eab91f11 1124 set = true;
b023850b 1125
eab91f11 1126 } else if(s == 5 && HAS('Z')) {
b023850b 1127 setCoolStepEnabled(GET('Z') == 1);
eab91f11 1128 set = true;
6e4b6254 1129 }
df16e9b0
JM
1130 }
1131
1132 return set;
1133}