Commit | Line | Data |
---|---|---|
3eca6882 JM |
1 | #include "MotorDriverControl.h" |
2 | #include "libs/Kernel.h" | |
3 | #include "libs/nuts_bolts.h" | |
4 | #include "libs/utils.h" | |
5 | #include "ConfigValue.h" | |
6 | #include "libs/StreamOutput.h" | |
5b0cf319 | 7 | #include "libs/StreamOutputPool.h" |
fc320ac5 JM |
8 | #include "Robot.h" |
9 | #include "StepperMotor.h" | |
7b35a4c8 | 10 | #include "PublicDataRequest.h" |
45ef09fd | 11 | |
3eca6882 JM |
12 | #include "Gcode.h" |
13 | #include "Config.h" | |
14 | #include "checksumm.h" | |
3eca6882 | 15 | |
5b0cf319 | 16 | #include "mbed.h" // for SPI |
3eca6882 | 17 | |
45ef09fd | 18 | #include "drivers/TMC26X/TMC26X.h" |
e21e1f3a | 19 | #include "drivers/DRV8711/drv8711.h" |
45ef09fd | 20 | |
3eca6882 | 21 | #include <string> |
3eca6882 | 22 | |
5b0cf319 JM |
23 | #define motor_driver_control_checksum CHECKSUM("motor_driver_control") |
24 | #define enable_checksum CHECKSUM("enable") | |
25 | #define chip_checksum CHECKSUM("chip") | |
fc320ac5 | 26 | #define designator_checksum CHECKSUM("designator") |
5262f8d0 JM |
27 | #define alarm_checksum CHECKSUM("alarm") |
28 | #define halt_on_alarm_checksum CHECKSUM("halt_on_alarm") | |
5b0cf319 JM |
29 | |
30 | #define current_checksum CHECKSUM("current") | |
31 | #define max_current_checksum CHECKSUM("max_current") | |
5b0cf319 | 32 | |
45ef09fd | 33 | #define microsteps_checksum CHECKSUM("microsteps") |
5b0cf319 | 34 | #define decay_mode_checksum CHECKSUM("decay_mode") |
3eca6882 | 35 | |
7b35a4c8 | 36 | #define raw_register_checksum CHECKSUM("reg") |
3eca6882 | 37 | |
45ef09fd JM |
38 | #define spi_channel_checksum CHECKSUM("spi_channel") |
39 | #define spi_cs_pin_checksum CHECKSUM("spi_cs_pin") | |
40 | #define spi_frequency_checksum CHECKSUM("spi_frequency") | |
3eca6882 | 41 | |
fc320ac5 | 42 | MotorDriverControl::MotorDriverControl(uint8_t id) : id(id) |
3eca6882 | 43 | { |
0bfaf040 | 44 | enable_event= false; |
b01faaff JM |
45 | current_override= false; |
46 | microstep_override= false; | |
3eca6882 JM |
47 | } |
48 | ||
49 | MotorDriverControl::~MotorDriverControl() | |
50 | { | |
3eca6882 JM |
51 | } |
52 | ||
53 | // this will load all motor driver controls defined in config, called from main | |
54 | void MotorDriverControl::on_module_loaded() | |
55 | { | |
56 | vector<uint16_t> modules; | |
57 | THEKERNEL->config->get_module_list( &modules, motor_driver_control_checksum ); | |
fc320ac5 | 58 | uint8_t cnt = 1; |
3eca6882 | 59 | for( auto cs : modules ) { |
5b0cf319 | 60 | // If module is enabled create an instance and initialize it |
3eca6882 | 61 | if( THEKERNEL->config->value(motor_driver_control_checksum, cs, enable_checksum )->as_bool() ) { |
fc320ac5 JM |
62 | MotorDriverControl *controller = new MotorDriverControl(cnt++); |
63 | if(!controller->config_module(cs)) delete controller; | |
3eca6882 JM |
64 | } |
65 | } | |
66 | ||
67 | // we don't need this instance anymore | |
68 | delete this; | |
69 | } | |
70 | ||
fc320ac5 | 71 | bool MotorDriverControl::config_module(uint16_t cs) |
3eca6882 | 72 | { |
fc320ac5 | 73 | std::string str= THEKERNEL->config->value( motor_driver_control_checksum, cs, designator_checksum)->by_default("")->as_string(); |
5b0cf319 JM |
74 | if(str.empty()) { |
75 | THEKERNEL->streams->printf("MotorDriverControl ERROR: designator not defined\n"); | |
76 | return false; // designator required | |
77 | } | |
3eca6882 JM |
78 | designator= str[0]; |
79 | ||
63b7731d JM |
80 | spi_cs_pin.from_string(THEKERNEL->config->value( motor_driver_control_checksum, cs, spi_cs_pin_checksum)->by_default("nc")->as_string())->as_output(); |
81 | if(!spi_cs_pin.connected()) { | |
82 | THEKERNEL->streams->printf("MotorDriverControl %c ERROR: chip select not defined\n", designator); | |
83 | return false; // if not defined then we can't use this instance | |
84 | } | |
85 | spi_cs_pin.set(1); | |
86 | ||
87 | ||
fc320ac5 | 88 | str= THEKERNEL->config->value( motor_driver_control_checksum, cs, chip_checksum)->by_default("")->as_string(); |
5b0cf319 | 89 | if(str.empty()) { |
63b7731d | 90 | THEKERNEL->streams->printf("MotorDriverControl %c ERROR: chip type not defined\n", designator); |
5b0cf319 JM |
91 | return false; // chip type required |
92 | } | |
93 | ||
45ef09fd JM |
94 | using std::placeholders::_1; |
95 | using std::placeholders::_2; | |
96 | using std::placeholders::_3; | |
97 | ||
5b0cf319 JM |
98 | if(str == "DRV8711") { |
99 | chip= DRV8711; | |
63b7731d | 100 | drv8711= new DRV8711DRV(std::bind( &MotorDriverControl::sendSPI, this, _1, _2, _3), designator); |
45ef09fd | 101 | |
5b0cf319 JM |
102 | }else if(str == "TMC2660") { |
103 | chip= TMC2660; | |
63b7731d | 104 | tmc26x= new TMC26X(std::bind( &MotorDriverControl::sendSPI, this, _1, _2, _3), designator); |
45ef09fd | 105 | |
5b0cf319 | 106 | }else{ |
63b7731d | 107 | THEKERNEL->streams->printf("MotorDriverControl %c ERROR: Unknown chip type: %s\n", designator, str.c_str()); |
5b0cf319 JM |
108 | return false; |
109 | } | |
110 | ||
3eca6882 | 111 | // select which SPI channel to use |
5b0cf319 | 112 | int spi_channel = THEKERNEL->config->value(motor_driver_control_checksum, cs, spi_channel_checksum)->by_default(1)->as_number(); |
3eca6882 JM |
113 | int spi_frequency = THEKERNEL->config->value(motor_driver_control_checksum, cs, spi_frequency_checksum)->by_default(1000000)->as_number(); |
114 | ||
45ef09fd | 115 | // select SPI channel to use |
3eca6882 JM |
116 | PinName mosi, miso, sclk; |
117 | if(spi_channel == 0) { | |
118 | mosi = P0_18; miso = P0_17; sclk = P0_15; | |
119 | } else if(spi_channel == 1) { | |
120 | mosi = P0_9; miso = P0_8; sclk = P0_7; | |
121 | } else { | |
63b7731d | 122 | THEKERNEL->streams->printf("MotorDriverControl %c ERROR: Unknown SPI Channel: %d\n", designator, spi_channel); |
5b0cf319 | 123 | return false; |
3eca6882 JM |
124 | } |
125 | ||
126 | this->spi = new mbed::SPI(mosi, miso, sclk); | |
127 | this->spi->frequency(spi_frequency); | |
fc320ac5 | 128 | this->spi->format(8, 3); // 8bit, mode3 |
3eca6882 | 129 | |
c611d712 JM |
130 | // set default max currents for each chip, can be overidden in config |
131 | switch(chip) { | |
132 | case DRV8711: max_current= 4000; break; | |
133 | case TMC2660: max_current= 4000; break; | |
134 | } | |
135 | ||
136 | max_current= THEKERNEL->config->value(motor_driver_control_checksum, cs, max_current_checksum )->by_default((int)max_current)->as_number(); // in mA | |
7b0f0de5 | 137 | //current_factor= THEKERNEL->config->value(motor_driver_control_checksum, cs, current_factor_checksum )->by_default(1.0F)->as_number(); |
5b0cf319 | 138 | |
7b0f0de5 JM |
139 | current= THEKERNEL->config->value(motor_driver_control_checksum, cs, current_checksum )->by_default(1000)->as_number(); // in mA |
140 | microsteps= THEKERNEL->config->value(motor_driver_control_checksum, cs, microsteps_checksum )->by_default(16)->as_number(); // 1/n | |
df16e9b0 | 141 | //decay_mode= THEKERNEL->config->value(motor_driver_control_checksum, cs, decay_mode_checksum )->by_default(1)->as_number(); |
5b0cf319 | 142 | |
45ef09fd | 143 | // setup the chip via SPI |
7daea248 | 144 | initialize_chip(cs); |
3eca6882 | 145 | |
7b35a4c8 JM |
146 | // if raw registers are defined set them 1,2,3 etc in hex |
147 | str= THEKERNEL->config->value( motor_driver_control_checksum, cs, raw_register_checksum)->by_default("")->as_string(); | |
148 | if(!str.empty()) { | |
149 | rawreg= true; | |
150 | std::vector<uint32_t> regs= parse_number_list(str.c_str(), 16); | |
30faab4a JM |
151 | if(!regs.empty()) { |
152 | uint32_t reg= 0; | |
153 | for(auto i : regs) { | |
154 | // this just sets the local storage, it does not write to the chip | |
155 | switch(chip) { | |
156 | case DRV8711: drv8711->set_raw_register(&StreamOutput::NullStream, ++reg, i); break; | |
157 | case TMC2660: tmc26x->setRawRegister(&StreamOutput::NullStream, ++reg, i); break; | |
158 | } | |
159 | } | |
160 | ||
161 | // write the stored registers | |
7b35a4c8 | 162 | switch(chip) { |
30faab4a JM |
163 | case DRV8711: drv8711->set_raw_register(&StreamOutput::NullStream, 255, 0); break; |
164 | case TMC2660: tmc26x->setRawRegister(&StreamOutput::NullStream, 255, 0); break; | |
7b35a4c8 JM |
165 | } |
166 | } | |
30faab4a | 167 | |
7b35a4c8 JM |
168 | }else{ |
169 | rawreg= false; | |
170 | } | |
171 | ||
3eca6882 | 172 | this->register_for_event(ON_GCODE_RECEIVED); |
7b35a4c8 | 173 | this->register_for_event(ON_HALT); |
0bfaf040 JM |
174 | this->register_for_event(ON_ENABLE); |
175 | this->register_for_event(ON_IDLE); | |
fc320ac5 | 176 | |
5262f8d0 JM |
177 | if( THEKERNEL->config->value(motor_driver_control_checksum, cs, alarm_checksum )->by_default(false)->as_bool() ) { |
178 | halt_on_alarm= THEKERNEL->config->value(motor_driver_control_checksum, cs, halt_on_alarm_checksum )->by_default(false)->as_bool(); | |
179 | // enable alarm monitoring for the chip | |
180 | this->register_for_event(ON_SECOND_TICK); | |
181 | } | |
182 | ||
fc320ac5 JM |
183 | THEKERNEL->streams->printf("MotorDriverControl INFO: configured motor %c (%d): as %s, cs: %04X\n", designator, id, chip==TMC2660?"TMC2660":chip==DRV8711?"DRV8711":"UNKNOWN", (spi_cs_pin.port_number<<8)|spi_cs_pin.pin); |
184 | ||
3eca6882 JM |
185 | return true; |
186 | } | |
187 | ||
0bfaf040 JM |
188 | // event to handle enable on/off, as it could be called in an ISR we schedule to turn the steppers on or off in ON_IDLE |
189 | // This may cause the initial step to be missed if on-idle is delayed too much but we can't do SPI in an interrupt | |
190 | void MotorDriverControl::on_enable(void *argument) | |
191 | { | |
192 | enable_event= true; | |
193 | enable_flg= (argument != nullptr); | |
194 | } | |
195 | ||
196 | void MotorDriverControl::on_idle(void *argument) | |
197 | { | |
198 | if(enable_event) { | |
199 | enable_event= false; | |
200 | enable(enable_flg); | |
201 | } | |
202 | } | |
203 | ||
7b35a4c8 | 204 | void MotorDriverControl::on_halt(void *argument) |
45ef09fd | 205 | { |
7b35a4c8 JM |
206 | if(argument == nullptr) { |
207 | enable(false); | |
45ef09fd JM |
208 | } |
209 | } | |
210 | ||
5ca112ee | 211 | // runs in on_idle, does SPI transaction |
5262f8d0 JM |
212 | void MotorDriverControl::on_second_tick(void *argument) |
213 | { | |
d74b7399 JM |
214 | // we don't want to keep checking once we have been halted by an error |
215 | if(THEKERNEL->is_halted()) return; | |
216 | ||
5262f8d0 JM |
217 | bool alarm=false;; |
218 | switch(chip) { | |
219 | case DRV8711: | |
220 | alarm= drv8711->check_alarm(); | |
221 | break; | |
222 | ||
223 | case TMC2660: | |
224 | alarm= tmc26x->checkAlarm(); | |
225 | break; | |
226 | } | |
227 | ||
228 | if(halt_on_alarm && alarm) { | |
229 | THEKERNEL->call_event(ON_HALT, nullptr); | |
230 | THEKERNEL->streams->printf("Motor Driver alarm - reset or M999 required to continue\r\n"); | |
231 | } | |
232 | } | |
233 | ||
3eca6882 JM |
234 | void MotorDriverControl::on_gcode_received(void *argument) |
235 | { | |
236 | Gcode *gcode = static_cast<Gcode*>(argument); | |
237 | ||
238 | if (gcode->has_m) { | |
45ef09fd | 239 | if(gcode->m == 906) { |
7b0f0de5 | 240 | if (gcode->has_letter(designator)) { |
45ef09fd | 241 | // set motor currents in mA (Note not using M907 as digipots use that) |
3eca6882 | 242 | current= gcode->get_value(designator); |
7b0f0de5 | 243 | current= std::min(current, max_current); |
3eca6882 | 244 | set_current(current); |
b01faaff | 245 | current_override= true; |
3eca6882 JM |
246 | } |
247 | ||
fc320ac5 | 248 | } else if(gcode->m == 909) { // M909 Annn set microstepping, M909.1 also change steps/mm |
5b0cf319 | 249 | if (gcode->has_letter(designator)) { |
fc320ac5 | 250 | uint32_t current_microsteps= microsteps; |
5b0cf319 | 251 | microsteps= gcode->get_value(designator); |
fc320ac5 JM |
252 | microsteps= set_microstep(microsteps); // driver may change the steps it sets to |
253 | if(gcode->subcode == 1 && current_microsteps != microsteps) { | |
254 | // also reset the steps/mm | |
255 | int a= designator-'A'; | |
256 | if(a >= 0 && a <=2) { | |
c8bac202 JM |
257 | float s= THEROBOT->actuators[a]->get_steps_per_mm()*((float)microsteps/current_microsteps); |
258 | THEROBOT->actuators[a]->change_steps_per_mm(s); | |
fc320ac5 | 259 | gcode->stream->printf("steps/mm for %c changed to: %f\n", designator, s); |
c8bac202 | 260 | THEROBOT->check_max_actuator_speeds(); |
fc320ac5 JM |
261 | } |
262 | } | |
b01faaff | 263 | microstep_override= true; |
5b0cf319 JM |
264 | } |
265 | ||
df16e9b0 JM |
266 | // } else if(gcode->m == 910) { // set decay mode |
267 | // if (gcode->has_letter(designator)) { | |
268 | // decay_mode= gcode->get_value(designator); | |
269 | // set_decay_mode(decay_mode); | |
270 | // } | |
5b0cf319 | 271 | |
7b0f0de5 | 272 | } else if(gcode->m == 911) { |
df16e9b0 | 273 | // set or get raw registers |
6e4b6254 | 274 | // M911 will dump all the registers and status of all the motors |
eab91f11 | 275 | // M911.1 Pn (or A0) will dump the registers and status of the selected motor. X0 will request format in processing machine readable format |
df16e9b0 | 276 | // M911.2 Pn (or B0) Rxxx Vyyy sets Register xxx to value yyy for motor nnn, xxx == 255 writes the registers, xxx == 0 shows what registers are mapped to what |
33483c6f JM |
277 | // M911.3 Pn (or C0) will set the options based on the parameters passed as below... |
278 | // TMC2660:- | |
279 | // M911.3 Onnn Qnnn setStallGuardThreshold O=stall_guard_threshold, Q=stall_guard_filter_enabled | |
33483c6f | 280 | // M911.3 Hnnn Innn Jnnn Knnn Lnnn setCoolStepConfiguration H=lower_SG_threshold, I=SG_hysteresis, J=current_decrement_step_size, K=current_increment_step_size, L=lower_current_limit |
6e4b6254 JM |
281 | // M911.3 S0 Unnn Vnnn Wnnn Xnnn Ynnn setConstantOffTimeChopper U=constant_off_time, V=blank_time, W=fast_decay_time_setting, X=sine_wave_offset, Y=use_current_comparator |
282 | // M911.3 S1 Unnn Vnnn Wnnn Xnnn Ynnn setSpreadCycleChopper U=constant_off_time, V=blank_time, W=hysteresis_start, X=hysteresis_end, Y=hysteresis_decrement | |
283 | // M911.3 S2 Zn setRandomOffTime Z=on|off Z1 is on Z0 is off | |
284 | // M911.3 S3 Zn setDoubleEdge Z=on|off Z1 is on Z0 is off | |
285 | // M911.3 S4 Zn setStepInterpolation Z=on|off Z1 is on Z0 is off | |
b023850b | 286 | // M911.3 S5 Zn setCoolStepEnabled Z=on|off Z1 is on Z0 is off |
33483c6f | 287 | |
df16e9b0 JM |
288 | if(gcode->subcode == 0 && gcode->get_num_args() == 0) { |
289 | // M911 no args dump status for all drivers, M911.1 P0|A0 dump for specific driver | |
7b0f0de5 | 290 | gcode->stream->printf("Motor %d (%c)...\n", id, designator); |
eab91f11 | 291 | dump_status(gcode->stream, true); |
7b0f0de5 | 292 | |
df16e9b0 JM |
293 | }else if(gcode->get_value('P') == id || gcode->has_letter(designator)) { |
294 | if(gcode->subcode == 1) { | |
eab91f11 | 295 | dump_status(gcode->stream, !gcode->has_letter('X')); |
df16e9b0 JM |
296 | |
297 | }else if(gcode->subcode == 2 && gcode->has_letter('R') && gcode->has_letter('V')) { | |
298 | set_raw_register(gcode->stream, gcode->get_value('R'), gcode->get_value('V')); | |
299 | ||
300 | }else if(gcode->subcode == 3 ) { | |
301 | set_options(gcode); | |
302 | } | |
7b0f0de5 JM |
303 | } |
304 | ||
3eca6882 | 305 | } else if(gcode->m == 500 || gcode->m == 503) { |
b01faaff JM |
306 | if(current_override) { |
307 | gcode->stream->printf(";Motor %c id %d current mA:\n", designator, id); | |
308 | gcode->stream->printf("M906 %c%lu\n", designator, current); | |
309 | } | |
310 | if(microstep_override) { | |
311 | gcode->stream->printf(";Motor %c id %d microsteps:\n", designator, id); | |
312 | gcode->stream->printf("M909 %c%lu\n", designator, microsteps); | |
313 | } | |
df16e9b0 | 314 | //gcode->stream->printf("M910 %c%d\n", designator, decay_mode); |
b01faaff | 315 | } |
3eca6882 JM |
316 | } |
317 | } | |
318 | ||
7daea248 | 319 | void MotorDriverControl::initialize_chip(uint16_t cs) |
45ef09fd JM |
320 | { |
321 | // send initialization sequence to chips | |
322 | if(chip == DRV8711) { | |
7daea248 | 323 | drv8711->init(cs); |
fbeb173d JM |
324 | set_current(current); |
325 | set_microstep(microsteps); | |
45ef09fd JM |
326 | |
327 | }else if(chip == TMC2660){ | |
7daea248 | 328 | tmc26x->init(cs); |
45ef09fd | 329 | set_current(current); |
e21e1f3a | 330 | set_microstep(microsteps); |
fbeb173d | 331 | //set_decay_mode(decay_mode); |
45ef09fd JM |
332 | } |
333 | ||
45ef09fd JM |
334 | } |
335 | ||
e21e1f3a | 336 | // set current in milliamps |
45ef09fd | 337 | void MotorDriverControl::set_current(uint32_t c) |
3eca6882 | 338 | { |
5b0cf319 | 339 | switch(chip) { |
e21e1f3a | 340 | case DRV8711: |
fbeb173d | 341 | drv8711->set_current(c); |
e21e1f3a | 342 | break; |
45ef09fd JM |
343 | |
344 | case TMC2660: | |
345 | tmc26x->setCurrent(c); | |
346 | break; | |
5b0cf319 JM |
347 | } |
348 | } | |
349 | ||
45ef09fd | 350 | // set microsteps where n is the number of microsteps eg 64 for 1/64 |
fc320ac5 | 351 | uint32_t MotorDriverControl::set_microstep( uint32_t n ) |
5b0cf319 | 352 | { |
fc320ac5 | 353 | uint32_t m= n; |
5b0cf319 | 354 | switch(chip) { |
e21e1f3a | 355 | case DRV8711: |
fbeb173d | 356 | m= drv8711->set_microsteps(n); |
e21e1f3a | 357 | break; |
45ef09fd JM |
358 | |
359 | case TMC2660: | |
360 | tmc26x->setMicrosteps(n); | |
fc320ac5 | 361 | m= tmc26x->getMicrosteps(); |
45ef09fd | 362 | break; |
5b0cf319 | 363 | } |
fc320ac5 | 364 | return m; |
5b0cf319 JM |
365 | } |
366 | ||
0bfaf040 | 367 | // TODO how to handle this? SO many options |
5b0cf319 JM |
368 | void MotorDriverControl::set_decay_mode( uint8_t dm ) |
369 | { | |
370 | switch(chip) { | |
371 | case DRV8711: break; | |
372 | case TMC2660: break; | |
373 | } | |
374 | } | |
375 | ||
45ef09fd JM |
376 | void MotorDriverControl::enable(bool on) |
377 | { | |
378 | switch(chip) { | |
e21e1f3a JM |
379 | case DRV8711: |
380 | drv8711->set_enable(on); | |
381 | break; | |
382 | ||
45ef09fd JM |
383 | case TMC2660: |
384 | tmc26x->setEnabled(on); | |
385 | break; | |
386 | } | |
387 | } | |
388 | ||
eab91f11 | 389 | void MotorDriverControl::dump_status(StreamOutput *stream, bool b) |
45ef09fd JM |
390 | { |
391 | switch(chip) { | |
e21e1f3a JM |
392 | case DRV8711: |
393 | drv8711->dump_status(stream); | |
394 | break; | |
395 | ||
45ef09fd | 396 | case TMC2660: |
eab91f11 | 397 | tmc26x->dumpStatus(stream, b); |
45ef09fd JM |
398 | break; |
399 | } | |
400 | } | |
df16e9b0 | 401 | |
7b0f0de5 JM |
402 | void MotorDriverControl::set_raw_register(StreamOutput *stream, uint32_t reg, uint32_t val) |
403 | { | |
404 | bool ok= false; | |
405 | switch(chip) { | |
5262f8d0 | 406 | case DRV8711: ok= drv8711->set_raw_register(stream, reg, val); break; |
7b0f0de5 JM |
407 | case TMC2660: ok= tmc26x->setRawRegister(stream, reg, val); break; |
408 | } | |
409 | if(ok) { | |
df16e9b0 | 410 | stream->printf("register operation succeeded\n"); |
7b0f0de5 JM |
411 | }else{ |
412 | stream->printf("register operation failed\n"); | |
413 | } | |
414 | } | |
45ef09fd | 415 | |
df16e9b0 JM |
416 | void MotorDriverControl::set_options(Gcode *gcode) |
417 | { | |
418 | switch(chip) { | |
419 | case DRV8711: break; | |
420 | ||
421 | case TMC2660: { | |
422 | TMC26X::options_t options= gcode->get_args_int(); | |
423 | if(options.size() > 0) { | |
424 | if(tmc26x->set_options(options)) { | |
425 | gcode->stream->printf("options set\n"); | |
426 | }else{ | |
427 | gcode->stream->printf("failed to set any options\n"); | |
428 | } | |
429 | } | |
430 | // options.clear(); | |
431 | // if(tmc26x->get_optional(options)) { | |
432 | // // foreach optional value | |
433 | // for(auto &i : options) { | |
434 | // // print all current values of supported options | |
435 | // gcode->stream->printf("%c: %d ", i.first, i.second); | |
436 | // gcode->add_nl = true; | |
437 | // } | |
438 | // } | |
439 | } | |
440 | break; | |
441 | } | |
442 | } | |
443 | ||
0bfaf040 | 444 | // Called by the drivers codes to send and receive SPI data to/from the chip |
45ef09fd JM |
445 | int MotorDriverControl::sendSPI(uint8_t *b, int cnt, uint8_t *r) |
446 | { | |
447 | spi_cs_pin.set(0); | |
448 | for (int i = 0; i < cnt; ++i) { | |
449 | r[i]= spi->write(b[i]); | |
450 | } | |
451 | spi_cs_pin.set(1); | |
452 | return cnt; | |
453 | } | |
454 |