2 Highly modifed from....
4 TMC26X.cpp - - TMC26X Stepper library for Wiring/Arduino
6 based on the stepper library by Tom Igoe, et. al.
8 Copyright (c) 2011, Interactive Matter, Marcus Nowotny
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:
17 The above copyright notice and this permission notice shall be included in
18 all copies or substantial portions of the Software.
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
32 #include "StreamOutput.h"
34 #include "libs/StreamOutputPool.h"
36 #include "StepperMotor.h"
37 #include "ConfigValue.h"
39 #include "checksumm.h"
40 #include "StepTicker.h"
42 #define motor_driver_control_checksum CHECKSUM("motor_driver_control")
43 #define sense_resistor_checksum CHECKSUM("sense_resistor")
45 //! return value for TMC26X.getOverTemperature() if there is a overtemperature situation in the TMC chip
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.
51 #define TMC26X_OVERTEMPERATURE_PREWARING 1
52 //! return value for TMC26X.getOverTemperature() if there is a overtemperature shutdown in the TMC chip
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.
59 #define TMC26X_OVERTEMPERATURE_SHUTDOWN 2
61 //which values can be read out
63 * Selects to readout the microstep position from the motor.
66 #define TMC26X_READOUT_POSITION 0
68 * Selects to read out the StallGuard value of the motor.
71 #define TMC26X_READOUT_STALLGUARD 1
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()
76 #define TMC26X_READOUT_CURRENT 3
79 * Define to set the minimum current for CoolStep operation to 1/2 of the selected CS minium.
80 *\sa setCoolStepConfiguration()
82 #define COOL_STEP_HALF_CS_LIMIT 0
84 * Define to set the minimum current for CoolStep operation to 1/4 of the selected CS minium.
85 *\sa setCoolStepConfiguration()
87 #define COOL_STEP_QUARTDER_CS_LIMIT 1
90 //some default values used in initialization
91 #define DEFAULT_MICROSTEPPING_VALUE 32
93 //TMC26X register definitions
94 #define DRIVER_CONTROL_REGISTER 0x00000ul
95 #define CHOPPER_CONFIG_REGISTER 0x80000ul
96 #define COOL_STEP_REGISTER 0xA0000ul
97 #define STALL_GUARD2_LOAD_MEASURE_REGISTER 0xC0000ul
98 #define DRIVER_CONFIG_REGISTER 0xE0000ul
100 #define REGISTER_BIT_PATTERN 0xFFFFFul
102 //definitions for the driver control register DRVCTL
103 #define MICROSTEPPING_PATTERN 0x000Ful
104 #define STEP_INTERPOLATION 0x0200ul
105 #define DOUBLE_EDGE_STEP 0x0100ul
107 //definitions for the driver config register DRVCONF
108 #define READ_MICROSTEP_POSITION 0x0000ul
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
114 //definitions for the chopper config register
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
129 //definitions for cool step register
130 #define MINIMUM_CURRENT_FOURTH 0x8000ul
131 #define CURRENT_DOWN_STEP_SPEED_PATTERN 0x6000ul
132 #define SE_MAX_PATTERN 0x0F00ul
133 #define SE_CURRENT_STEP_WIDTH_PATTERN 0x0060ul
134 #define SE_MIN_PATTERN 0x000Ful
136 //definitions for stall guard2 current register
137 #define STALL_GUARD_FILTER_ENABLED 0x10000ul
138 #define STALL_GUARD_TRESHHOLD_VALUE_PATTERN 0x17F00ul
139 #define CURRENT_SCALING_PATTERN 0x0001Ful
140 #define STALL_GUARD_CONFIG_PATTERN 0x17F00ul
141 #define STALL_GUARD_VALUE_PATTERN 0x07F00ul
143 //definitions for the input from the TCM260
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
160 TMC26X::TMC26X(std::function
<int(uint8_t *b
, int cnt
, uint8_t *r
)> spi
, char d
) : spi(spi
), designator(d
)
162 //we are not started yet
164 //by default cool step is not enabled
165 cool_step_enabled
= false;
166 error_reported
.reset();
170 * configure the stepper driver
171 * just must be called.
173 void TMC26X::init(uint16_t cs
)
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
178 //setting the default register values
179 driver_control_register_value
= DRIVER_CONTROL_REGISTER
;
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
;
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
);
195 //set to a conservative start value
196 setConstantOffTimeChopper(7, 54, 13, 12, 1);
198 //void TMC26X::setSpreadCycleChopper( constant_off_time, blank_time, hysteresis_start, hysteresis_end, hysteresis_decrement);
200 // openbuilds high torque nema23 3amps (2.8)
201 setSpreadCycleChopper(5, 36, 6, 0, 0);
202 // for 1.5amp kysan @ 12v
203 setSpreadCycleChopper(5, 54, 5, 0, 0);
204 // for 4amp Nema24 @ 12v
205 //setSpreadCycleChopper(5, 54, 4, 0, 0);
210 //set a nice microstepping value
211 setMicrosteps(DEFAULT_MICROSTEPPING_VALUE
);
213 // set stallguard to a conservative value so it doesn't trigger immediately
214 setStallGuardThreshold(10, 1);
217 void TMC26X::setCurrent(unsigned int current
)
219 uint8_t current_scaling
= 0;
220 //calculate the current scaling from the max current setting (in mA)
221 double mASetting
= (double)current
;
222 double resistor_value
= (double) this->resistor
;
223 // remove vesense flag
224 this->driver_configuration_register_value
&= ~(VSENSE
);
225 //this is derrived from I=(cs+1)/32*(Vsense/Rsense)
226 //leading to cs = CS = 32*R*I/V (with V = 0,31V oder 0,165V and I = 1000*current)
228 //for vsense = 0,310V (VSENSE not set)
229 //or vsense = 0,165V (VSENSE set)
230 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
232 //check if the current scaling is too low
233 if (current_scaling
< 16) {
234 //set the csense bit to get a use half the sense voltage (to support lower motor currents)
235 this->driver_configuration_register_value
|= VSENSE
;
236 //and recalculate the current setting
237 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
240 //do some sanity checks
241 if (current_scaling
> 31) {
242 current_scaling
= 31;
244 //delete the old value
245 stall_guard2_current_register_value
&= ~(CURRENT_SCALING_PATTERN
);
246 //set the new current scaling
247 stall_guard2_current_register_value
|= current_scaling
;
248 //if started we directly send it to the motor
250 send262(driver_configuration_register_value
);
251 send262(stall_guard2_current_register_value
);
255 unsigned int TMC26X::getCurrent(void)
257 //we calculate the current according to the datasheet to be on the safe side
258 //this is not the fastest but the most accurate and illustrative way
259 double result
= (double)(stall_guard2_current_register_value
& CURRENT_SCALING_PATTERN
);
260 double resistor_value
= (double)this->resistor
;
261 double voltage
= (driver_configuration_register_value
& VSENSE
) ? 0.165F
: 0.31F
;
262 result
= (result
+ 1.0F
) / 32.0F
* voltage
/ resistor_value
* 1000.0F
* 1000.0F
;
263 return (unsigned int)result
;
266 void TMC26X::setStallGuardThreshold(int8_t stall_guard_threshold
, int8_t stall_guard_filter_enabled
)
268 if (stall_guard_threshold
< -64) {
269 stall_guard_threshold
= -64;
270 //We just have 5 bits
271 } else if (stall_guard_threshold
> 63) {
272 stall_guard_threshold
= 63;
274 //add trim down to 7 bits
275 stall_guard_threshold
&= 0x7f;
276 //delete old stall guard settings
277 stall_guard2_current_register_value
&= ~(STALL_GUARD_CONFIG_PATTERN
);
278 if (stall_guard_filter_enabled
) {
279 stall_guard2_current_register_value
|= STALL_GUARD_FILTER_ENABLED
;
281 //Set the new stall guard threshold
282 stall_guard2_current_register_value
|= (((unsigned long)stall_guard_threshold
<< 8) & STALL_GUARD_CONFIG_PATTERN
);
283 //if started we directly send it to the motor
285 send262(stall_guard2_current_register_value
);
289 int8_t TMC26X::getStallGuardThreshold(void)
291 unsigned long stall_guard_threshold
= stall_guard2_current_register_value
& STALL_GUARD_VALUE_PATTERN
;
292 //shift it down to bit 0
293 stall_guard_threshold
>>= 8;
294 //convert the value to an int to correctly handle the negative numbers
295 int8_t result
= stall_guard_threshold
;
296 //check if it is negative and fill it up with leading 1 for proper negative number representation
297 if (result
& (1 << 6)) {
303 int8_t TMC26X::getStallGuardFilter(void)
305 if (stall_guard2_current_register_value
& STALL_GUARD_FILTER_ENABLED
) {
312 * Set the number of microsteps per step.
313 * 0,2,4,8,16,32,64,128,256 is supported
314 * any value in between will be mapped to the next smaller value
315 * 0 and 1 set the motor in full step mode
317 void TMC26X::setMicrosteps(int number_of_steps
)
319 long setting_pattern
;
321 if (number_of_steps
>= 256) {
324 } else if (number_of_steps
>= 128) {
327 } else if (number_of_steps
>= 64) {
330 } else if (number_of_steps
>= 32) {
333 } else if (number_of_steps
>= 16) {
336 } else if (number_of_steps
>= 8) {
339 } else if (number_of_steps
>= 4) {
342 } else if (number_of_steps
>= 2) {
345 //1 and 0 lead to full step
346 } else if (number_of_steps
<= 1) {
351 //delete the old value
352 this->driver_control_register_value
&= 0xFFFF0ul
;
354 this->driver_control_register_value
|= setting_pattern
;
356 //if started we directly send it to the motor
358 send262(driver_control_register_value
);
363 * returns the effective number of microsteps at the moment
365 int TMC26X::getMicrosteps(void)
370 void TMC26X::setStepInterpolation(int8_t value
)
373 driver_control_register_value
|= STEP_INTERPOLATION
;
375 driver_control_register_value
&= ~(STEP_INTERPOLATION
);
377 //if started we directly send it to the motor
379 send262(driver_control_register_value
);
383 void TMC26X::setDoubleEdge(int8_t value
)
386 driver_control_register_value
|= DOUBLE_EDGE_STEP
;
388 driver_control_register_value
&= ~(DOUBLE_EDGE_STEP
);
390 //if started we directly send it to the motor
392 send262(driver_control_register_value
);
397 * constant_off_time: The off time setting controls the minimum chopper frequency.
398 * For most applications an off time within the range of 5μs to 20μs will fit.
399 * 2...15: off time setting
401 * blank_time: Selects the comparator blank time. This time needs to safely cover the switching event and the
402 * duration of the ringing on the sense resistor. For
403 * 0: min. setting 3: max. setting
405 * fast_decay_time_setting: Fast decay time setting. With CHM=1, these bits control the portion of fast decay for each chopper cycle.
407 * 1...15: duration of fast decay phase
409 * sine_wave_offset: Sine wave offset. With CHM=1, these bits control the sine wave offset.
410 * A positive offset corrects for zero crossing error.
411 * -3..-1: negative offset 0: no offset 1...12: positive offset
413 * use_current_comparator: Selects usage of the current comparator for termination of the fast decay cycle.
414 * If current comparator is enabled, it terminates the fast decay cycle in case the current
415 * reaches a higher negative value than the actual positive value.
416 * 1: enable comparator termination of fast decay cycle
417 * 0: end by time only
419 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
)
421 //perform some sanity checks
422 if (constant_off_time
< 2) {
423 constant_off_time
= 2;
424 } else if (constant_off_time
> 15) {
425 constant_off_time
= 15;
427 //save the constant off time
428 this->constant_off_time
= constant_off_time
;
430 //calculate the value acc to the clock cycles
431 if (blank_time
>= 54) {
433 } else if (blank_time
>= 36) {
435 } else if (blank_time
>= 24) {
440 this->blank_time
= blank_time
;
442 if (fast_decay_time_setting
< 0) {
443 fast_decay_time_setting
= 0;
444 } else if (fast_decay_time_setting
> 15) {
445 fast_decay_time_setting
= 15;
447 if (sine_wave_offset
< -3) {
448 sine_wave_offset
= -3;
449 } else if (sine_wave_offset
> 12) {
450 sine_wave_offset
= 12;
452 //shift the sine_wave_offset
453 sine_wave_offset
+= 3;
455 //calculate the register setting
456 //first of all delete all the values for this
457 chopper_config_register
&= ~((1 << 12) | BLANK_TIMING_PATTERN
| HYSTERESIS_DECREMENT_PATTERN
| HYSTERESIS_LOW_VALUE_PATTERN
| HYSTERESIS_START_VALUE_PATTERN
| T_OFF_TIMING_PATERN
);
458 //set the constant off pattern
459 chopper_config_register
|= CHOPPER_MODE_T_OFF_FAST_DECAY
;
460 //set the blank timing value
461 chopper_config_register
|= ((unsigned long)blank_value
) << BLANK_TIMING_SHIFT
;
462 //setting the constant off time
463 chopper_config_register
|= constant_off_time
;
464 //set the fast decay time
466 chopper_config_register
|= (((unsigned long)(fast_decay_time_setting
& 0x8)) << HYSTERESIS_DECREMENT_SHIFT
);
468 chopper_config_register
|= (((unsigned long)(fast_decay_time_setting
& 0x7)) << HYSTERESIS_START_VALUE_SHIFT
);
469 //set the sine wave offset
470 chopper_config_register
|= (unsigned long)sine_wave_offset
<< HYSTERESIS_LOW_SHIFT
;
471 //using the current comparator?
472 if (!use_current_comparator
) {
473 chopper_config_register
|= (1 << 12);
475 //if started we directly send it to the motor
477 send262(chopper_config_register
);
482 * constant_off_time: The off time setting controls the minimum chopper frequency.
483 * For most applications an off time within the range of 5μs to 20μs will fit.
484 * 2...15: off time setting
486 * blank_time: Selects the comparator blank time. This time needs to safely cover the switching event and the
487 * duration of the ringing on the sense resistor. For
488 * 0: min. setting 3: max. setting
490 * hysteresis_start: Hysteresis start setting. Please remark, that this value is an offset to the hysteresis end value HEND.
493 * hysteresis_end: Hysteresis end setting. Sets the hysteresis end value after a number of decrements. Decrement interval time is controlled by HDEC.
494 * The sum HSTRT+HEND must be <16. At a current setting CS of max. 30 (amplitude reduced to 240), the sum is not limited.
495 * -3..-1: negative HEND 0: zero HEND 1...12: positive HEND
497 * hysteresis_decrement: Hysteresis decrement setting. This setting determines the slope of the hysteresis during on time and during fast decay time.
498 * 0: fast decrement 3: very slow decrement
501 void TMC26X::setSpreadCycleChopper(int8_t constant_off_time
, int8_t blank_time
, int8_t hysteresis_start
, int8_t hysteresis_end
, int8_t hysteresis_decrement
)
503 h_start
= hysteresis_start
;
504 h_end
= hysteresis_end
;
505 h_decrement
= hysteresis_decrement
;
506 this->blank_time
= blank_time
;
508 //perform some sanity checks
509 if (constant_off_time
< 2) {
510 constant_off_time
= 2;
511 } else if (constant_off_time
> 15) {
512 constant_off_time
= 15;
514 //save the constant off time
515 this->constant_off_time
= constant_off_time
;
517 //calculate the value acc to the clock cycles
518 if (blank_time
>= 54) {
520 } else if (blank_time
>= 36) {
522 } else if (blank_time
>= 24) {
528 if (hysteresis_start
< 1) {
529 hysteresis_start
= 1;
530 } else if (hysteresis_start
> 8) {
531 hysteresis_start
= 8;
535 if (hysteresis_end
< -3) {
537 } else if (hysteresis_end
> 12) {
540 //shift the hysteresis_end
543 if (hysteresis_decrement
< 0) {
544 hysteresis_decrement
= 0;
545 } else if (hysteresis_decrement
> 3) {
546 hysteresis_decrement
= 3;
549 //first of all delete all the values for this
550 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
);
552 //set the blank timing value
553 chopper_config_register
|= ((unsigned long)blank_value
) << BLANK_TIMING_SHIFT
;
554 //setting the constant off time
555 chopper_config_register
|= constant_off_time
;
556 //set the hysteresis_start
557 chopper_config_register
|= ((unsigned long)hysteresis_start
) << HYSTERESIS_START_VALUE_SHIFT
;
558 //set the hysteresis end
559 chopper_config_register
|= ((unsigned long)hysteresis_end
) << HYSTERESIS_LOW_SHIFT
;
560 //set the hystereis decrement
561 chopper_config_register
|= ((unsigned long)hysteresis_decrement
) << HYSTERESIS_DECREMENT_SHIFT
;
563 //if started we directly send it to the motor
565 send262(chopper_config_register
);
570 * In a constant off time chopper scheme both coil choppers run freely, i.e. are not synchronized.
571 * 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.
572 * 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
573 * few microstep positions within each quarter wave. This effect normally is not audible when compared to mechanical noise generated by ball bearings, etc.
574 * Further factors which can cause a similar effect are a poor layout of sense resistor GND connection.
575 * Hint: A common factor, which can cause motor noise, is a bad PCB layout causing coupling of both sense resistor voltages
576 * (please refer to sense resistor layout hint in chapter 8.1).
577 * In order to minimize the effect of a beat between both chopper frequencies, an internal random generator is provided.
578 * It modulates the slow decay time setting when switched on by the RNDTF bit. The RNDTF feature further spreads the chopper spectrum,
579 * reducing electromagnetic emission on single frequencies.
581 void TMC26X::setRandomOffTime(int8_t value
)
584 chopper_config_register
|= RANDOM_TOFF_TIME
;
586 chopper_config_register
&= ~(RANDOM_TOFF_TIME
);
588 //if started we directly send it to the motor
590 send262(chopper_config_register
);
594 void TMC26X::setCoolStepConfiguration(unsigned int lower_SG_threshold
, unsigned int SG_hysteresis
, uint8_t current_decrement_step_size
,
595 uint8_t current_increment_step_size
, uint8_t lower_current_limit
)
597 //sanitize the input values
598 if (lower_SG_threshold
> 480) {
599 lower_SG_threshold
= 480;
602 lower_SG_threshold
>>= 5;
603 if (SG_hysteresis
> 480) {
608 if (current_decrement_step_size
> 3) {
609 current_decrement_step_size
= 3;
611 if (current_increment_step_size
> 3) {
612 current_increment_step_size
= 3;
614 if (lower_current_limit
> 1) {
615 lower_current_limit
= 1;
617 //store the lower level in order to enable/disable the cool step
618 this->cool_step_lower_threshold
= lower_SG_threshold
;
619 //if cool step is not enabled we delete the lower value to keep it disabled
620 if (!this->cool_step_enabled
) {
621 lower_SG_threshold
= 0;
623 //the good news is that we can start with a complete new cool step register value
624 //and simply set the values in the register
625 cool_step_register_value
= ((unsigned long)lower_SG_threshold
) | (((unsigned long)SG_hysteresis
) << 8) | (((unsigned long)current_decrement_step_size
) << 5)
626 | (((unsigned long)current_increment_step_size
) << 13) | (((unsigned long)lower_current_limit
) << 15)
627 //and of course we have to include the signature of the register
628 | COOL_STEP_REGISTER
;
631 send262(cool_step_register_value
);
635 void TMC26X::setCoolStepEnabled(bool enabled
)
637 //simply delete the lower limit to disable the cool step
638 cool_step_register_value
&= ~SE_MIN_PATTERN
;
639 //and set it to the proper value if cool step is to be enabled
641 cool_step_register_value
|= this->cool_step_lower_threshold
;
643 //and save the enabled status
644 this->cool_step_enabled
= enabled
;
645 //save the register value
647 send262(cool_step_register_value
);
651 bool TMC26X::isCoolStepEnabled(void)
653 return this->cool_step_enabled
;
656 unsigned int TMC26X::getCoolStepLowerSgThreshold()
658 //we return our internally stored value - in order to provide the correct setting even if cool step is not enabled
659 return this->cool_step_lower_threshold
<< 5;
662 unsigned int TMC26X::getCoolStepUpperSgThreshold()
664 return (uint8_t)((cool_step_register_value
& SE_MAX_PATTERN
) >> 8) << 5;
667 uint8_t TMC26X::getCoolStepCurrentIncrementSize()
669 return (uint8_t)((cool_step_register_value
& CURRENT_DOWN_STEP_SPEED_PATTERN
) >> 13);
672 uint8_t TMC26X::getCoolStepNumberOfSGReadings()
674 return (uint8_t)((cool_step_register_value
& SE_CURRENT_STEP_WIDTH_PATTERN
) >> 5);
677 uint8_t TMC26X::getCoolStepLowerCurrentLimit()
679 return (uint8_t)((cool_step_register_value
& MINIMUM_CURRENT_FOURTH
) >> 15);
682 void TMC26X::setEnabled(bool enabled
)
684 //delete the t_off in the chopper config to get sure
685 chopper_config_register
&= ~(T_OFF_PATTERN
);
687 //and set the t_off time
688 chopper_config_register
|= this->constant_off_time
;
690 //if not enabled we don't have to do anything since we already delete t_off from the register
692 send262(chopper_config_register
);
696 bool TMC26X::isEnabled()
698 if (chopper_config_register
& T_OFF_PATTERN
) {
706 * reads a value from the TMC26X status register. The value is not obtained directly but can then
707 * be read by the various status routines.
710 void TMC26X::readStatus(int8_t read_value
)
712 unsigned long old_driver_configuration_register_value
= driver_configuration_register_value
;
713 //reset the readout configuration
714 driver_configuration_register_value
&= ~(READ_SELECTION_PATTERN
);
715 //this now equals TMC26X_READOUT_POSITION - so we just have to check the other two options
716 if (read_value
== TMC26X_READOUT_STALLGUARD
) {
717 driver_configuration_register_value
|= READ_STALL_GUARD_READING
;
718 } else if (read_value
== TMC26X_READOUT_CURRENT
) {
719 driver_configuration_register_value
|= READ_STALL_GUARD_AND_COOL_STEP
;
721 //all other cases are ignored to prevent funny values
722 //check if the readout is configured for the value we are interested in
723 if (driver_configuration_register_value
!= old_driver_configuration_register_value
) {
724 //because then we need to write the value twice - one time for configuring, second time to get the value, see below
725 send262(driver_configuration_register_value
);
727 //write the configuration to get the last status
728 send262(driver_configuration_register_value
);
731 //reads the stall guard setting from last status
732 //returns -1 if stallguard information is not present
733 int TMC26X::getCurrentStallGuardReading(void)
735 //if we don't yet started there cannot be a stall guard value
739 //not time optimal, but solution optiomal:
740 //first read out the stall guard value
741 readStatus(TMC26X_READOUT_STALLGUARD
);
742 return getReadoutValue();
745 uint8_t TMC26X::getCurrentCSReading(void)
747 //if we don't yet started there cannot be a stall guard value
752 //first read out the stall guard value
753 readStatus(TMC26X_READOUT_CURRENT
);
754 return (getReadoutValue() & 0x1f);
757 unsigned int TMC26X::getCoolstepCurrent(void)
759 float result
= (float)getCurrentCSReading();
760 float resistor_value
= (float)this->resistor
;
761 float voltage
= (driver_configuration_register_value
& VSENSE
) ? 0.165F
: 0.31F
;
762 result
= (result
+ 1.0F
) / 32.0F
* voltage
/ resistor_value
* 1000.0F
* 1000.0F
;
763 return (unsigned int)roundf(result
);
767 return true if the stallguard threshold has been reached
769 bool TMC26X::isStallGuardOverThreshold(void)
771 if (!this->started
) {
774 return (driver_status_result
& STATUS_STALL_GUARD_STATUS
);
778 returns if there is any over temperature condition:
779 OVER_TEMPERATURE_PREWARING if pre warning level has been reached
780 OVER_TEMPERATURE_SHUTDOWN if the temperature is so hot that the driver is shut down
781 Any of those levels are not too good.
783 int8_t TMC26X::getOverTemperature(void)
785 if (!this->started
) {
788 if (driver_status_result
& STATUS_OVER_TEMPERATURE_SHUTDOWN
) {
789 return TMC26X_OVERTEMPERATURE_SHUTDOWN
;
791 if (driver_status_result
& STATUS_OVER_TEMPERATURE_WARNING
) {
792 return TMC26X_OVERTEMPERATURE_PREWARING
;
797 //is motor channel A shorted to ground
798 bool TMC26X::isShortToGroundA(void)
800 if (!this->started
) {
803 return (driver_status_result
& STATUS_SHORT_TO_GROUND_A
);
806 //is motor channel B shorted to ground
807 bool TMC26X::isShortToGroundB(void)
809 if (!this->started
) {
812 return (driver_status_result
& STATUS_SHORT_TO_GROUND_B
);
815 //is motor channel A connected
816 bool TMC26X::isOpenLoadA(void)
818 if (!this->started
) {
821 return (driver_status_result
& STATUS_OPEN_LOAD_A
);
824 //is motor channel B connected
825 bool TMC26X::isOpenLoadB(void)
827 if (!this->started
) {
830 return (driver_status_result
& STATUS_OPEN_LOAD_B
);
833 //is chopper inactive since 2^20 clock cycles - defaults to ~0,08s
834 bool TMC26X::isStandStill(void)
836 if (!this->started
) {
839 return (driver_status_result
& STATUS_STAND_STILL
);
842 //is chopper inactive since 2^20 clock cycles - defaults to ~0,08s
843 bool TMC26X::isStallGuardReached(void)
845 if (!this->started
) {
848 return (driver_status_result
& STATUS_STALL_GUARD_STATUS
);
851 //reads the stall guard setting from last status
852 //returns -1 if stallguard inforamtion is not present
853 int TMC26X::getReadoutValue(void)
855 return (int)(driver_status_result
>> 10);
858 bool TMC26X::isCurrentScalingHalfed()
860 if (this->driver_configuration_register_value
& VSENSE
) {
867 void TMC26X::dumpStatus(StreamOutput
*stream
, bool readable
)
870 stream
->printf("designator %c, Chip type TMC26X\n", designator
);
872 check_error_status_bits(stream
);
874 if (this->isStallGuardReached()) {
875 stream
->printf("INFO: Stall Guard level reached!\n");
878 if (this->isStandStill()) {
879 stream
->printf("INFO: Motor is standing still.\n");
882 int value
= getReadoutValue();
883 stream
->printf("Microstep position phase A: %d\n", value
);
885 value
= getCurrentStallGuardReading();
886 stream
->printf("Stall Guard value: %d\n", value
);
888 stream
->printf("Current setting: %dmA\n", getCurrent());
889 stream
->printf("Coolstep current: %dmA\n", getCoolstepCurrent());
891 stream
->printf("Microsteps: 1/%d\n", microsteps
);
893 stream
->printf("Register dump:\n");
894 stream
->printf(" driver control register: %08lX(%ld)\n", driver_control_register_value
, driver_control_register_value
);
895 stream
->printf(" chopper config register: %08lX(%ld)\n", chopper_config_register
, chopper_config_register
);
896 stream
->printf(" cool step register: %08lX(%ld)\n", cool_step_register_value
, cool_step_register_value
);
897 stream
->printf(" stall guard2 current register: %08lX(%ld)\n", stall_guard2_current_register_value
, stall_guard2_current_register_value
);
898 stream
->printf(" driver configuration register: %08lX(%ld)\n", driver_configuration_register_value
, driver_configuration_register_value
);
899 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
);
902 // TODO hardcoded for X need to select ABC as needed
903 bool moving
= THEROBOT
->actuators
[0]->is_moving();
904 // dump out in the format that the processing script needs
906 stream
->printf("#sg%d,p%lu,k%u,r,", getCurrentStallGuardReading(), THEROBOT
->actuators
[0]->get_current_step(), getCoolstepCurrent());
908 readStatus(TMC26X_READOUT_POSITION
); // get the status bits
909 stream
->printf("#s,");
911 stream
->printf("d%d,", THEROBOT
->actuators
[0]->which_direction() ? 1 : -1);
912 stream
->printf("c%u,m%d,", getCurrent(), getMicrosteps());
913 // stream->printf('S');
914 // stream->printf(tmc26XStepper.getSpeed(), DEC);
915 stream
->printf("t%d,f%d,", getStallGuardThreshold(), getStallGuardFilter());
917 //print out the general cool step config
918 if (isCoolStepEnabled()) stream
->printf("Ke+,");
919 else stream
->printf("Ke-,");
921 stream
->printf("Kl%u,Ku%u,Kn%u,Ki%u,Km%u,",
922 getCoolStepLowerSgThreshold(), getCoolStepUpperSgThreshold(), getCoolStepNumberOfSGReadings(), getCoolStepCurrentIncrementSize(), getCoolStepLowerCurrentLimit());
924 //detect the winding status
926 stream
->printf("ao,");
927 } else if(isShortToGroundA()) {
928 stream
->printf("ag,");
930 stream
->printf("a-,");
932 //detect the winding status
934 stream
->printf("bo,");
935 } else if(isShortToGroundB()) {
936 stream
->printf("bg,");
938 stream
->printf("b-,");
941 char temperature
= getOverTemperature();
942 if (temperature
== 0) {
943 stream
->printf("x-,");
944 } else if (temperature
== TMC26X_OVERTEMPERATURE_PREWARING
) {
945 stream
->printf("xw,");
947 stream
->printf("xe,");
951 stream
->printf("e1,");
953 stream
->printf("e0,");
956 //write out the current chopper config
957 stream
->printf("Cm%d,", (chopper_config_register
& CHOPPER_MODE_T_OFF_FAST_DECAY
) != 0);
958 stream
->printf("Co%d,Cb%d,", constant_off_time
, blank_time
);
959 if ((chopper_config_register
& CHOPPER_MODE_T_OFF_FAST_DECAY
) == 0) {
960 stream
->printf("Cs%d,Ce%d,Cd%d,", h_start
, h_end
, h_decrement
);
962 stream
->printf("\n");
966 // check error bits and report, only report once
967 bool TMC26X::check_error_status_bits(StreamOutput
*stream
)
970 readStatus(TMC26X_READOUT_POSITION
); // get the status bits
972 if (this->getOverTemperature()&TMC26X_OVERTEMPERATURE_PREWARING
) {
973 if(!error_reported
.test(0)) stream
->printf("%c - WARNING: Overtemperature Prewarning!\n", designator
);
974 error_reported
.set(0);
976 error_reported
.reset(0);
979 if (this->getOverTemperature()&TMC26X_OVERTEMPERATURE_SHUTDOWN
) {
980 if(!error_reported
.test(1)) stream
->printf("%c - ERROR: Overtemperature Shutdown!\n", designator
);
982 error_reported
.set(1);
984 error_reported
.reset(1);
987 if (this->isShortToGroundA()) {
988 if(!error_reported
.test(2)) stream
->printf("%c - ERROR: SHORT to ground on channel A!\n", designator
);
990 error_reported
.set(2);
992 error_reported
.reset(2);
995 if (this->isShortToGroundB()) {
996 if(!error_reported
.test(3)) stream
->printf("%c - ERROR: SHORT to ground on channel B!\n", designator
);
998 error_reported
.set(3);
1000 error_reported
.reset(3);
1003 // these seem to be triggered when moving so ignore them for now
1004 if (this->isOpenLoadA()) {
1005 if(!error_reported
.test(4)) stream
->printf("%c - ERROR: Channel A seems to be unconnected!\n", designator
);
1007 error_reported
.set(4);
1009 error_reported
.reset(4);
1012 if (this->isOpenLoadB()) {
1013 if(!error_reported
.test(5)) stream
->printf("%c - ERROR: Channel B seems to be unconnected!\n", designator
);
1015 error_reported
.set(5);
1017 error_reported
.reset(5);
1021 // stream->printf("%08X\n", driver_status_result);
1026 bool TMC26X::checkAlarm()
1028 return check_error_status_bits(THEKERNEL
->streams
);
1031 // sets a raw register to the value specified, for advanced settings
1032 // register 255 writes them, 0 displays what registers are mapped to what
1033 // FIXME status registers not reading back correctly, check docs
1034 bool TMC26X::setRawRegister(StreamOutput
*stream
, uint32_t reg
, uint32_t val
)
1038 send262(driver_control_register_value
);
1039 send262(chopper_config_register
);
1040 send262(cool_step_register_value
);
1041 send262(stall_guard2_current_register_value
);
1042 send262(driver_configuration_register_value
);
1043 stream
->printf("Registers written\n");
1047 case 1: driver_control_register_value
= val
; stream
->printf("driver control register set to %08lX\n", val
); break;
1048 case 2: chopper_config_register
= val
; stream
->printf("chopper config register set to %08lX\n", val
); break;
1049 case 3: cool_step_register_value
= val
; stream
->printf("cool step register set to %08lX\n", val
); break;
1050 case 4: stall_guard2_current_register_value
= val
; stream
->printf("stall guard2 current register set to %08lX\n", val
); break;
1051 case 5: driver_configuration_register_value
= val
; stream
->printf("driver configuration register set to %08lX\n", val
); break;
1054 stream
->printf("1: driver control register\n");
1055 stream
->printf("2: chopper config register\n");
1056 stream
->printf("3: cool step register\n");
1057 stream
->printf("4: stall guard2 current register\n");
1058 stream
->printf("5: driver configuration register\n");
1059 stream
->printf("255: update all registers\n");
1066 * send register settings to the stepper driver via SPI
1067 * returns the current status
1068 * sends 20bits, the last 20 bits of the 24bits is taken as the command
1070 void TMC26X::send262(unsigned long datagram
)
1072 uint8_t buf
[] {(uint8_t)(datagram
>> 16), (uint8_t)(datagram
>> 8), (uint8_t)(datagram
& 0xff)};
1075 //write/read the values
1079 unsigned long i_datagram
= ((rbuf
[0] << 16) | (rbuf
[1] << 8) | (rbuf
[2])) >> 4;
1081 //store the datagram as status result
1082 driver_status_result
= i_datagram
;
1084 //THEKERNEL->streams->printf("sent: %02X, %02X, %02X received: %02X, %02X, %02X \n", buf[0], buf[1], buf[2], rbuf[0], rbuf[1], rbuf[2]);
1087 #define HAS(X) (options.find(X) != options.end())
1088 #define GET(X) (options.at(X))
1089 bool TMC26X::set_options(const options_t
& options
)
1092 if(HAS('O') || HAS('Q')) {
1093 // void TMC26X::setStallGuardThreshold(int8_t stall_guard_threshold, int8_t stall_guard_filter_enabled)
1094 int8_t o
= HAS('O') ? GET('O') : getStallGuardThreshold();
1095 int8_t q
= HAS('Q') ? GET('Q') : getStallGuardFilter();
1096 setStallGuardThreshold(o
, q
);
1100 if(HAS('H') && HAS('I') && HAS('J') && HAS('K') && HAS('L')) {
1101 //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)
1102 setCoolStepConfiguration(GET('H'), GET('I'), GET('J'), GET('K'), GET('L'));
1107 uint32_t s
= GET('S');
1108 if(s
== 0 && HAS('U') && HAS('V') && HAS('W') && HAS('X') && HAS('Y')) {
1109 //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)
1110 setConstantOffTimeChopper(GET('U'), GET('V'), GET('W'), GET('X'), GET('Y'));
1113 } else if(s
== 1 && HAS('U') && HAS('V') && HAS('W') && HAS('X') && HAS('Y')) {
1114 //void TMC26X::setSpreadCycleChopper(int8_t constant_off_time, int8_t blank_time, int8_t hysteresis_start, int8_t hysteresis_end, int8_t hysteresis_decrement);
1115 setSpreadCycleChopper(GET('U'), GET('V'), GET('W'), GET('X'), GET('Y'));
1118 } else if(s
== 2 && HAS('Z')) {
1119 setRandomOffTime(GET('Z'));
1122 } else if(s
== 3 && HAS('Z')) {
1123 setDoubleEdge(GET('Z'));
1126 } else if(s
== 4 && HAS('Z')) {
1127 setStepInterpolation(GET('Z'));
1130 } else if(s
== 5 && HAS('Z')) {
1131 setCoolStepEnabled(GET('Z') == 1);