-
Notifications
You must be signed in to change notification settings - Fork 126
Expand file tree
/
Copy pathtmc_motor_controller.cpp
More file actions
741 lines (625 loc) · 27.4 KB
/
tmc_motor_controller.cpp
File metadata and controls
741 lines (625 loc) · 27.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
#include "software/embedded/motor_controller/tmc_motor_controller.h"
#include "shared/constants.h"
#include "software/embedded/gpio/gpio_char_dev.h"
#include "software/embedded/spi_utils.h"
#include "software/logger/logger.h"
extern "C"
{
#include "tmc/ic/TMC4671/TMC4671.h"
#include "tmc/ic/TMC4671/TMC4671_Register.h"
#include "tmc/ic/TMC4671/TMC4671_Variants.h"
#include "tmc/ic/TMC6100/TMC6100.h"
}
#include <linux/spi/spidev.h>
#include <bitset>
#include <numeric>
extern "C"
{
// We need a static pointer here, because trinamic externs the following two
// SPI binding functions that we need to interface with their API.
//
// The motor service exclusively calls the trinamic API which triggers these
// functions. The motor service will set this variable in the constructor.
static TmcMotorController* g_motor_controller = nullptr;
uint8_t tmc4671_readwriteByte(uint8_t motor, uint8_t data, uint8_t last_transfer)
{
return g_motor_controller->tmc4671ReadWriteByte(motor, data, last_transfer);
}
uint8_t tmc6100_readwriteByte(uint8_t motor, uint8_t data, uint8_t last_transfer)
{
return g_motor_controller->tmc6100ReadWriteByte(motor, data, last_transfer);
}
}
TmcMotorController::TmcMotorController()
: spi_demux_select_0_(std::make_unique<GpioCharDev>(
SPI_CS_DRIVER_TO_CONTROLLER_MUX_0_GPIO, GpioDirection::OUTPUT, GpioState::LOW)),
spi_demux_select_1_(std::make_unique<GpioCharDev>(
SPI_CS_DRIVER_TO_CONTROLLER_MUX_1_GPIO, GpioDirection::OUTPUT, GpioState::LOW)),
driver_control_enable_gpio_(std::make_unique<GpioCharDev>(
DRIVER_CONTROL_ENABLE_GPIO, GpioDirection::OUTPUT, GpioState::HIGH)),
reset_gpio_(std::make_unique<GpioCharDev>(MOTOR_DRIVER_RESET_GPIO,
GpioDirection::OUTPUT, GpioState::HIGH))
{
openSpiFileDescriptor(MotorIndex::FRONT_LEFT);
openSpiFileDescriptor(MotorIndex::FRONT_RIGHT);
openSpiFileDescriptor(MotorIndex::BACK_LEFT);
openSpiFileDescriptor(MotorIndex::BACK_RIGHT);
openSpiFileDescriptor(MotorIndex::DRIBBLER);
// Make this instance available to the static functions above
g_motor_controller = this;
}
int TmcMotorController::readThenWriteVelocity(const MotorIndex motor,
const int target_velocity)
{
if (motor == MotorIndex::DRIBBLER)
{
const int velocity_erpm = readThenWriteValue(
motor, TMC4671_PID_VELOCITY_ACTUAL, TMC4671_PID_VELOCITY_TARGET,
target_velocity * DRIBBLER_MOTOR_ELECTRICAL_RPM_PER_MECHANICAL_RPM);
return velocity_erpm * DRIBBLER_MOTOR_MECHANICAL_RPM_PER_ELECTRICAL_RPM;
}
else
{
const int velocity_erpm = readThenWriteValue(
motor, TMC4671_PID_VELOCITY_ACTUAL, TMC4671_PID_VELOCITY_TARGET,
target_velocity * DRIVE_MOTOR_ELECTRICAL_RPM_PER_MECHANICAL_RPM);
return velocity_erpm * DRIVE_MOTOR_MECHANICAL_RPM_PER_ELECTRICAL_RPM;
}
}
void TmcMotorController::updateEuclideanVelocity(
EuclideanSpace_t target_euclidean_velocity)
{
}
void TmcMotorController::writeToDriverOrDieTrying(const uint8_t motor,
const uint8_t address,
const int32_t value)
{
int num_retries_left = NUM_RETRIES_SPI;
int read_value = 0;
// The SPI lines have a lot of noise, and sometimes a transfer will fail
// randomly. So we retry a few times before giving up.
while (num_retries_left > 0)
{
tmc6100_writeInt(motor, address, value);
read_value = tmc6100_readInt(motor, address);
if (read_value == value)
{
return;
}
LOG(DEBUG) << "SPI Transfer to Driver Failed, retrying...";
num_retries_left--;
}
// If we get here, we have failed to write to the driver. We reset
// the chip to clear any bad values we just wrote and crash so everything stops.
reset_gpio_->setValue(GpioState::LOW);
CHECK(read_value == value) << "Couldn't write " << value
<< " to the TMC6100 at address " << address
<< " at address " << static_cast<uint32_t>(address)
<< " on motor " << static_cast<uint32_t>(motor)
<< " received: " << read_value;
}
void TmcMotorController::writeToControllerOrDieTrying(const MotorIndex motor,
const uint8_t address,
const int32_t value)
{
int num_retries_left = NUM_RETRIES_SPI;
int read_value = 0;
// The SPI lines have a lot of noise, and sometimes a transfer will fail
// randomly. So we retry a few times before giving up.
while (num_retries_left > 0)
{
tmc4671_writeInt(CHIP_SELECTS.at(motor), address, value);
read_value = tmc4671_readInt(CHIP_SELECTS.at(motor), address);
if (read_value == value)
{
return;
}
LOG(DEBUG) << "SPI Transfer to Controller Failed, retrying...";
num_retries_left--;
}
}
void TmcMotorController::setup()
{
reset_gpio_->setValue(GpioState::LOW);
usleep(MICROSECONDS_PER_MILLISECOND * 100);
reset_gpio_->setValue(GpioState::HIGH);
usleep(MICROSECONDS_PER_MILLISECOND * 100);
for (const MotorIndex motor : reflective_enum::values<MotorIndex>())
{
motor_faults_[motor] = MotorFaultIndicator();
num_motor_fault_checks_[motor] = 0;
}
for (const MotorIndex motor : reflective_enum::values<MotorIndex>())
{
LOG(INFO) << "Clearing RESET for " << motor;
tmc6100_writeInt(CHIP_SELECTS.at(motor), TMC6100_GSTAT, 0x00000001);
encoder_calibrated_[motor] = false;
}
// Drive Motor Setup
for (const MotorIndex motor : driveMotors())
{
setupDriveMotor(motor);
}
// Dribbler Motor Setup
startDriver(MotorIndex::DRIBBLER);
checkFaults(MotorIndex::DRIBBLER);
startController(MotorIndex::DRIBBLER, true);
tmc4671_setTargetVelocity(DRIBBLER_MOTOR_CHIP_SELECT, 0);
checkEncoderConnections();
// calibrate the encoders
for (const MotorIndex motor : driveMotors())
{
startEncoderCalibration(motor);
}
sleep(1);
for (const MotorIndex motor : driveMotors())
{
endEncoderCalibration(motor);
}
auto motors = driveMotors();
bool has_encoders_calibrated =
std::accumulate(motors.begin(), motors.end(), false,
[&](const bool& acc, const MotorIndex motor)
{ return acc || encoder_calibrated_[motor]; });
CHECK(has_encoders_calibrated)
<< "Running without encoder calibration can cause serious harm, exiting";
}
void TmcMotorController::configurePWM(const MotorIndex motor)
{
LOG(INFO) << "Configuring PWM for motor " << static_cast<uint32_t>(motor);
// Please read the header file and the datasheet for more info
writeToControllerOrDieTrying(motor, TMC4671_PWM_POLARITIES, 0x00000000);
writeToControllerOrDieTrying(motor, TMC4671_PWM_MAXCNT, 0x00000F9F);
writeToControllerOrDieTrying(motor, TMC4671_PWM_BBM_H_BBM_L, 0x00002828);
writeToControllerOrDieTrying(motor, TMC4671_PWM_SV_CHOP, 0x00000107);
}
void TmcMotorController::configureDrivePI(const MotorIndex motor)
{
LOG(INFO) << "Configuring Drive PI for motor " << static_cast<uint32_t>(motor);
// Please read the header file and the datasheet for more info
// These values were calibrated using the TMC-IDE
writeToControllerOrDieTrying(motor, TMC4671_PID_FLUX_P_FLUX_I, 67109376);
writeToControllerOrDieTrying(motor, TMC4671_PID_TORQUE_P_TORQUE_I, 67109376);
writeToControllerOrDieTrying(motor, TMC4671_PID_VELOCITY_P_VELOCITY_I, 52428800);
// Explicitly disable the position controller
writeToControllerOrDieTrying(motor, TMC4671_PID_POSITION_P_POSITION_I, 0);
writeToControllerOrDieTrying(motor, TMC4671_PIDOUT_UQ_UD_LIMITS, 32767);
writeToControllerOrDieTrying(motor, TMC4671_PID_TORQUE_FLUX_LIMITS, 2500);
writeToControllerOrDieTrying(motor, TMC4671_PID_ACCELERATION_LIMIT, 1000);
writeToControllerOrDieTrying(motor, TMC4671_PID_VELOCITY_LIMIT, 45000);
tmc4671_switchToMotionMode(CHIP_SELECTS.at(motor), TMC4671_MOTION_MODE_VELOCITY);
}
void TmcMotorController::configureDribblerPI(const MotorIndex motor)
{
LOG(INFO) << "Configuring Dribbler PI for motor " << static_cast<uint32_t>(motor);
// Please read the header file and the datasheet for more info
// These values were calibrated using the TMC-IDE
writeToControllerOrDieTrying(motor, TMC4671_PID_FLUX_P_FLUX_I, 39337600);
writeToControllerOrDieTrying(motor, TMC4671_PID_TORQUE_P_TORQUE_I, 39333600);
writeToControllerOrDieTrying(motor, TMC4671_PID_VELOCITY_P_VELOCITY_I, 2621448);
// Explicitly disable the position controller
writeToControllerOrDieTrying(motor, TMC4671_PID_POSITION_P_POSITION_I, 0);
writeToControllerOrDieTrying(motor, TMC4671_PIDOUT_UQ_UD_LIMITS, 32767);
// TODO (#2677) support MAX_FORCE mode. This value can go up to 4.8 amps but we set it
// to 2 for now (sufficient for INDEFINITE mode).
writeToControllerOrDieTrying(motor, TMC4671_PID_TORQUE_FLUX_LIMITS, 4000);
writeToControllerOrDieTrying(motor, TMC4671_PID_ACCELERATION_LIMIT, 40000);
writeToControllerOrDieTrying(motor, TMC4671_PID_VELOCITY_LIMIT, 15000);
}
void TmcMotorController::configureADC(const MotorIndex motor)
{
LOG(INFO) << "Configuring ADC for motor " << static_cast<uint32_t>(motor);
// ADC configuration
writeToControllerOrDieTrying(motor, TMC4671_ADC_I_SELECT, 0x18000100);
writeToControllerOrDieTrying(motor, TMC4671_dsADC_MDEC_B_MDEC_A, 0x014E014E);
// These values have been calibrated for the TI INA240 current sense amplifier.
// The scaling is also set to work with both the drive and dribbler motors.
//
// If you wish to use the TMC4671+TMC6100-BOB you can use the following values,
// that work for the AD8418 current sense amplifier
//
// TMC4671_ADC_I0_SCALE_OFFSET = 0x010081DD
// TMC4671_ADC_I1_SCALE_OFFSET = 0x0100818E
//
writeToControllerOrDieTrying(motor, TMC4671_ADC_I0_SCALE_OFFSET, 0x000981DD);
writeToControllerOrDieTrying(motor, TMC4671_ADC_I1_SCALE_OFFSET, 0x0009818E);
}
void TmcMotorController::configureEncoder(const MotorIndex motor)
{
LOG(INFO) << "Configuring Encoder for motor " << static_cast<uint32_t>(motor);
// ABN encoder settings
writeToControllerOrDieTrying(motor, TMC4671_ABN_DECODER_MODE, 0x00000000);
writeToControllerOrDieTrying(motor, TMC4671_ABN_DECODER_PPR, 0x00001000);
}
void TmcMotorController::configureHall(const MotorIndex motor)
{
LOG(INFO) << "Configuring Hall for motor " << static_cast<uint32_t>(motor);
// Digital hall settings
writeToControllerOrDieTrying(motor, TMC4671_HALL_MODE, 0x00000000);
writeToControllerOrDieTrying(motor, TMC4671_HALL_PHI_E_PHI_M_OFFSET, 0x00000000);
// Feedback selection
writeToControllerOrDieTrying(motor, TMC4671_PHI_E_SELECTION, TMC4671_PHI_E_HALL);
writeToControllerOrDieTrying(motor, TMC4671_VELOCITY_SELECTION,
TMC4671_VELOCITY_PHI_E_HAL);
}
const MotorFaultIndicator& TmcMotorController::checkFaults(const MotorIndex motor)
{
num_motor_fault_checks_[motor]++;
// To reduce the number of SPI transactions, we only check for faults every
// MOTOR_FAULT_CHECK_INTERVAL calls and return the cached faults otherwise.
if (num_motor_fault_checks_.at(motor) % MOTOR_FAULT_CHECK_INTERVAL == 0)
{
return motor_faults_.at(motor);
}
MotorFaultIndicator& motor_faults = motor_faults_.at(motor);
motor_faults.faults.clear();
const int gstat = tmc6100_readInt(CHIP_SELECTS.at(motor), TMC6100_GSTAT);
const std::bitset<32> gstat_bitset(gstat);
if (gstat_bitset.any())
{
LOG(WARNING) << "======= Faults For Motor " << motor << "=======";
}
if (gstat_bitset[0])
{
LOG(WARNING)
<< "Indicates that the IC has been reset. All registers have been cleared to reset values."
<< "Attention: DRV_EN must be high to allow clearing reset";
motor_faults.faults.insert(TbotsProto::MotorFault::RESET);
}
if (gstat_bitset[1])
{
LOG(WARNING)
<< "drv_otpw : Indicates, that the driver temperature has exceeded overtemperature prewarning-level."
<< "No action is taken. This flag is latched.";
motor_faults.faults.insert(
TbotsProto::MotorFault::DRIVER_OVERTEMPERATURE_PREWARNING);
}
if (gstat_bitset[2])
{
LOG(WARNING)
<< "drv_ot: Indicates, that the driver has been shut down due to overtemperature."
<< "This flag can only be cleared when the temperature is below the limit again."
<< "It is latched for information.";
motor_faults.faults.insert(TbotsProto::MotorFault::DRIVER_OVERTEMPERATURE);
}
if (gstat_bitset[3])
{
LOG(WARNING) << "uv_cp: Indicates an undervoltage on the charge pump."
<< "The driver is disabled during undervoltage."
<< "This flag is latched for information.";
motor_faults.faults.insert(TbotsProto::MotorFault::UNDERVOLTAGE_CHARGEPUMP);
motor_faults.drive_enabled = false;
}
if (gstat_bitset[4])
{
LOG(WARNING) << "shortdet_u: Short to GND detected on phase U."
<< "The driver becomes disabled until flag becomes cleared.";
motor_faults.faults.insert(
TbotsProto::MotorFault::PHASE_U_SHORT_COUNTER_DETECTED);
motor_faults.drive_enabled = false;
}
if (gstat_bitset[5])
{
LOG(WARNING) << "s2gu: Short to GND detected on phase U."
<< "The driver becomes disabled until flag becomes cleared.";
motor_faults.faults.insert(TbotsProto::MotorFault::PHASE_U_SHORT_TO_GND_DETECTED);
motor_faults.drive_enabled = false;
}
if (gstat_bitset[6])
{
LOG(WARNING) << "s2vsu: Short to VS detected on phase U."
<< "The driver becomes disabled until flag becomes cleared.";
motor_faults.faults.insert(TbotsProto::MotorFault::PHASE_U_SHORT_TO_VS_DETECTED);
motor_faults.drive_enabled = false;
}
if (gstat_bitset[8])
{
LOG(WARNING) << "shortdet_v: V short counter has triggered at least once.";
motor_faults.faults.insert(
TbotsProto::MotorFault::PHASE_V_SHORT_COUNTER_DETECTED);
}
if (gstat_bitset[9])
{
LOG(WARNING) << "s2gv: Short to GND detected on phase V."
<< "The driver becomes disabled until flag becomes cleared.";
motor_faults.faults.insert(TbotsProto::MotorFault::PHASE_V_SHORT_TO_GND_DETECTED);
motor_faults.drive_enabled = false;
}
if (gstat_bitset[10])
{
LOG(WARNING) << "s2vsv: Short to VS detected on phase V."
<< "The driver becomes disabled until flag becomes cleared.";
motor_faults.faults.insert(TbotsProto::MotorFault::PHASE_V_SHORT_TO_VS_DETECTED);
motor_faults.drive_enabled = false;
}
if (gstat_bitset[12])
{
LOG(WARNING) << "shortdet_w: short counter has triggered at least once.";
motor_faults.faults.insert(
TbotsProto::MotorFault::PHASE_W_SHORT_COUNTER_DETECTED);
}
if (gstat_bitset[13])
{
LOG(WARNING) << "s2gw: Short to GND detected on phase W."
<< "The driver becomes disabled until flag becomes cleared.";
motor_faults.faults.insert(TbotsProto::MotorFault::PHASE_W_SHORT_TO_GND_DETECTED);
motor_faults.drive_enabled = false;
}
if (gstat_bitset[14])
{
LOG(WARNING) << "s2vsw: Short to VS detected on phase W."
<< "The driver becomes disabled until flag becomes cleared.";
motor_faults.faults.insert(TbotsProto::MotorFault::PHASE_W_SHORT_TO_VS_DETECTED);
motor_faults.drive_enabled = false;
}
return motor_faults;
}
int TmcMotorController::readThenWriteValue(const MotorIndex motor,
const uint8_t read_addr,
const uint8_t write_addr, const int write_data)
{
spi_demux_select_0_->setValue(GpioState::HIGH);
spi_demux_select_1_->setValue(GpioState::LOW);
// Trinamic transactions looks like this:
// + - - - + - - - + - - - + - - - + - - - +
// | ADDR | DATA |
// + - - - + - - - + - - - + - - - + - - - +
// 0 1 2 3 4
// Also it is in BIG Endian, therefore MSB is leftmost bit of 0.
// For a write, MSB must be 1, for read, MSB must be 0
// https://github.com/trinamic/TMC-API/blob/master/tmc/ic/TMC4671/TMC4671.c
read_tx_[0] = read_addr & 0x7f;
write_tx_[0] = write_addr | 0x80;
// Convert from little endian to big endian
for (int i = 3; i >= 0; i--)
{
uint8_t byte_to_copy = (uint8_t)(0xff & (write_data >> 8 * i));
write_tx_[4 - i] = byte_to_copy;
}
readThenWriteSpiTransfer<TMC_CMD_MSG_SIZE, TMC_CMD_MSG_SIZE>(
file_descriptors_[CHIP_SELECTS.at(motor)], read_tx_, write_tx_, read_rx_,
TMC4671_SPI_SPEED);
int32_t value = read_rx_[0];
for (int i = 1; i < 5; i++)
{
value <<= 8;
value |= read_rx_[i];
}
return value;
}
void TmcMotorController::openSpiFileDescriptor(const MotorIndex motor_index)
{
file_descriptors_[CHIP_SELECTS.at(motor_index)] =
open(SPI_PATHS.at(motor_index), O_RDWR);
CHECK(file_descriptors_[CHIP_SELECTS.at(motor_index)] >= 0)
<< "can't open device: " << motor_index << "error: " << strerror(errno);
int ret = ioctl(file_descriptors_[CHIP_SELECTS.at(motor_index)], SPI_IOC_WR_MODE32,
&SPI_MODE);
CHECK(ret != -1) << "can't set spi mode for: " << motor_index
<< "error: " << strerror(errno);
ret = ioctl(file_descriptors_[CHIP_SELECTS.at(motor_index)], SPI_IOC_WR_BITS_PER_WORD,
&SPI_BITS);
CHECK(ret != -1) << "can't set bits_per_word for: " << motor_index
<< "error: " << strerror(errno);
ret = ioctl(file_descriptors_[CHIP_SELECTS.at(motor_index)], SPI_IOC_WR_MAX_SPEED_HZ,
&MAX_SPI_SPEED_HZ);
CHECK(ret != -1) << "can't set spi max speed hz for: " << motor_index
<< "error: " << strerror(errno);
}
void TmcMotorController::setupDriveMotor(const MotorIndex motor)
{
startDriver(motor);
checkFaults(motor);
// Start all the controllers as drive motor controllers
startController(motor, false);
tmc4671_setTargetVelocity(CHIP_SELECTS.at(motor), 0);
}
void TmcMotorController::startDriver(const MotorIndex motor)
{
uint8_t motor_cs = CHIP_SELECTS.at(motor);
// Set the drive strength to 0, the weakest it can go as recommended
// by the TMC4671-TMC6100-BOB datasheet.
int32_t current_drive_conf = tmc6100_readInt(motor_cs, TMC6100_DRV_CONF);
writeToDriverOrDieTrying(motor_cs, TMC6100_DRV_CONF,
current_drive_conf & (~TMC6100_DRVSTRENGTH_MASK));
writeToDriverOrDieTrying(motor_cs, TMC6100_GCONF, 0x40);
// All default but updated SHORTFILTER to 2us to avoid false positive shorts
// detection.
writeToDriverOrDieTrying(motor_cs, TMC6100_SHORT_CONF, 0x13020606);
LOG(DEBUG) << "Driver " << motor << " accepted conf";
}
void TmcMotorController::startController(const MotorIndex motor, const bool dribbler)
{
// Read the chip ID to validate the SPI connection
tmc4671_writeInt(CHIP_SELECTS.at(motor), TMC4671_CHIPINFO_ADDR, 0x000000000);
int chip_id = tmc4671_readInt(CHIP_SELECTS.at(motor), TMC4671_CHIPINFO_DATA);
CHECK(0x34363731 == chip_id)
<< "The TMC4671 of motor " << motor << " is not responding";
LOG(DEBUG) << "Controller " << motor << " online, responded with: " << chip_id;
// Configure common controller params
configurePWM(motor);
configureADC(motor);
if (dribbler)
{
// Configure to brushless DC motor with 1 pole pair
writeToControllerOrDieTrying(motor, TMC4671_MOTOR_TYPE_N_POLE_PAIRS,
0x00030000 + DRIBBLER_MOTOR_NUM_POLE_PAIRS);
configureHall(motor);
configureDribblerPI(motor);
}
else
{
// Configure to brushless DC motor with 8 pole pairs
writeToControllerOrDieTrying(motor, TMC4671_MOTOR_TYPE_N_POLE_PAIRS,
0x00030000 + DRIVE_MOTOR_NUM_POLE_PAIRS);
configureEncoder(motor);
}
}
uint8_t TmcMotorController::tmc4671ReadWriteByte(const uint8_t motor, const uint8_t data,
const uint8_t last_transfer)
{
spi_demux_select_0_->setValue(GpioState::HIGH);
spi_demux_select_1_->setValue(GpioState::LOW);
return readWriteByte(motor, data, last_transfer, TMC4671_SPI_SPEED);
}
uint8_t TmcMotorController::tmc6100ReadWriteByte(const uint8_t motor, const uint8_t data,
const uint8_t last_transfer)
{
spi_demux_select_0_->setValue(GpioState::LOW);
spi_demux_select_1_->setValue(GpioState::HIGH);
return readWriteByte(motor, data, last_transfer, TMC6100_SPI_SPEED);
}
uint8_t TmcMotorController::readWriteByte(const uint8_t motor, const uint8_t data,
const uint8_t last_transfer,
const uint32_t spi_speed)
{
uint8_t ret_byte = 0;
if (!transfer_started_)
{
position_ = 0;
if (data & TMC_WRITE_BIT)
{
// If the transfer started and its a write operation,
// set the appropriate flags.
currently_reading_ = false;
currently_writing_ = true;
}
else
{
// The first byte should contain the address on a read operation.
// Trigger a transfer (1 byte) and buffer the response (4 bytes)
tx_[position_] = data;
spiTransfer<5>(file_descriptors_[motor], tx_, rx_, spi_speed);
currently_reading_ = true;
currently_writing_ = false;
}
transfer_started_ = true;
}
if (currently_writing_)
{
// Buffer the data to send out when last_transfer is true.
tx_[position_++] = data;
}
if (currently_reading_)
{
// If we are reading, we just need to return the buffered data
// byte by byte.
ret_byte = rx_[position_++];
}
if (currently_writing_ && last_transfer)
{
// we have all the bytes for this transfer, lets trigger the transfer and
// reset state
spiTransfer<5>(file_descriptors_[motor], tx_, rx_, spi_speed);
transfer_started_ = false;
}
if (currently_reading_ && last_transfer)
{
// when reading, if last transfer is true, we just need to reset state
transfer_started_ = false;
}
return ret_byte;
}
void TmcMotorController::checkEncoderConnections()
{
LOG(INFO) << "Starting encoder connection check!";
std::unordered_map<MotorIndex, bool> calibrated_motors;
std::unordered_map<MotorIndex, int> initial_velocities;
for (const MotorIndex motor : driveMotors())
{
calibrated_motors[motor] = false;
// read back current velocity
initial_velocities[motor] =
tmc4671_readInt(CHIP_SELECTS.at(motor), TMC4671_ABN_DECODER_COUNT);
// open loop mode can be used without an encoder, set open loop phi positive
// direction
writeToControllerOrDieTrying(motor, TMC4671_OPENLOOP_MODE, 0x00000000);
writeToControllerOrDieTrying(motor, TMC4671_PHI_E_SELECTION,
TMC4671_PHI_E_OPEN_LOOP);
writeToControllerOrDieTrying(motor, TMC4671_OPENLOOP_ACCELERATION, 0x0000003C);
// represents effective voltage applied to the motors (% voltage)
writeToControllerOrDieTrying(motor, TMC4671_UQ_UD_EXT, 0x00000799);
// uq_ud_ext mode
writeToControllerOrDieTrying(motor, TMC4671_MODE_RAMP_MODE_MOTION, 0x00000008);
// 10 RPM
writeToControllerOrDieTrying(motor, TMC4671_OPENLOOP_VELOCITY_TARGET, 0x0000000A);
}
for (int num_iterations = 0;
num_iterations < 10 &&
std::any_of(calibrated_motors.begin(), calibrated_motors.end(),
[](std::pair<const MotorIndex, bool> calibration_status_pair)
{ return !calibration_status_pair.second; });
++num_iterations)
{
for (const MotorIndex motor : driveMotors())
{
if (calibrated_motors[motor])
{
continue;
}
// now read back the velocity
int read_back_velocity =
tmc4671_readInt(CHIP_SELECTS.at(motor), TMC4671_ABN_DECODER_COUNT);
LOG(INFO) << motor << " read back: " << read_back_velocity
<< " and initially read: " << initial_velocities[motor];
if (read_back_velocity != initial_velocities[motor])
{
calibrated_motors[motor] = true;
}
}
// sleep for 100 milliseconds
usleep(MICROSECONDS_PER_MILLISECOND * 100);
}
bool calibrated = true;
for (const MotorIndex motor : driveMotors())
{
if (!calibrated_motors[motor])
{
calibrated = false;
LOG(WARNING) << "Encoder calibration check failure. " << motor
<< " did not change as expected";
}
}
if (!calibrated)
{
LOG(FATAL)
<< "Encoder calibration check failure. Not all encoders responded as expected";
}
// stop all motors, reset back to velocity control mode
for (const MotorIndex motor : driveMotors())
{
writeToControllerOrDieTrying(motor, TMC4671_OPENLOOP_VELOCITY_TARGET, 0x00000000);
tmc4671_switchToMotionMode(CHIP_SELECTS.at(motor), TMC4671_MOTION_MODE_VELOCITY);
}
LOG(INFO) << "All encoders appear to be connected!";
}
void TmcMotorController::reset()
{
reset_gpio_->setValue(GpioState::LOW);
}
void TmcMotorController::startEncoderCalibration(const MotorIndex motor)
{
LOG(WARNING) << "Calibrating the encoder, ensure the robot is lifted off the ground";
writeToControllerOrDieTrying(motor, TMC4671_PID_TORQUE_FLUX_LIMITS, 0x000003E8);
writeToControllerOrDieTrying(motor, TMC4671_PID_TORQUE_P_TORQUE_I, 0x01000100);
writeToControllerOrDieTrying(motor, TMC4671_PID_FLUX_P_FLUX_I, 0x01000100);
writeToControllerOrDieTrying(motor, TMC4671_MODE_RAMP_MODE_MOTION, 0x00000008);
writeToControllerOrDieTrying(motor, TMC4671_ABN_DECODER_PHI_E_PHI_M_OFFSET,
0x00000000);
writeToControllerOrDieTrying(motor, TMC4671_PHI_E_SELECTION, 0x00000001);
writeToControllerOrDieTrying(motor, TMC4671_PHI_E_EXT, 0x00000000);
writeToControllerOrDieTrying(motor, TMC4671_UQ_UD_EXT, 0x00000FFF);
}
void TmcMotorController::endEncoderCalibration(const MotorIndex motor)
{
LOG(WARNING) << "Calibrating the encoder, wheels may move";
writeToControllerOrDieTrying(motor, TMC4671_ABN_DECODER_COUNT, 0x00000000);
writeToControllerOrDieTrying(motor, TMC4671_UQ_UD_EXT, 0x00000000);
writeToControllerOrDieTrying(motor, TMC4671_PHI_E_SELECTION, TMC4671_PHI_E_ABN);
encoder_calibrated_[motor] = true;
configureDrivePI(motor);
}
void TmcMotorController::immediatelyDisable()
{
driver_control_enable_gpio_->setValue(GpioState::LOW);
}