motor_task.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. #include <SimpleFOC.h>
  2. #include <sensors/MagneticSensorI2C.h>
  3. #include "motor_task.h"
  4. #include "mt6701_sensor.h"
  5. #include "tlv_sensor.h"
  6. #include "util.h"
  7. static const float DEAD_ZONE_DETENT_PERCENT = 0.2;
  8. static const float DEAD_ZONE_RAD = 1 * _PI / 180;
  9. static const float IDLE_VELOCITY_EWMA_ALPHA = 0.001;
  10. static const float IDLE_VELOCITY_RAD_PER_SEC = 0.05;
  11. static const uint32_t IDLE_CORRECTION_DELAY_MILLIS = 500;
  12. static const float IDLE_CORRECTION_MAX_ANGLE_RAD = 5 * PI / 180;
  13. static const float IDLE_CORRECTION_RATE_ALPHA = 0.0005;
  14. MotorTask::MotorTask(const uint8_t task_core) : Task("Motor", 1200, 1, task_core) {
  15. queue_ = xQueueCreate(5, sizeof(Command));
  16. assert(queue_ != NULL);
  17. }
  18. MotorTask::~MotorTask() {}
  19. // BLDC motor & driver instance
  20. BLDCMotor motor = BLDCMotor(1);
  21. BLDCDriver6PWM driver = BLDCDriver6PWM(PIN_UH, PIN_UL, PIN_VH, PIN_VL, PIN_WH, PIN_WL);
  22. #if SENSOR_TLV
  23. TlvSensor encoder = TlvSensor();
  24. #elif SENSOR_MT6701
  25. MT6701Sensor encoder = MT6701Sensor();
  26. #endif
  27. // MagneticSensorI2C tlv = MagneticSensorI2C(AS5600_I2C);
  28. Commander command = Commander(Serial);
  29. void doMotor(char* cmd) { command.motor(&motor, cmd); }
  30. void MotorTask::run() {
  31. // Hardware-specific configuration:
  32. // TODO: make this easier to configure
  33. // Tune zero offset to the specific hardware (motor + mounted magnetic sensor).
  34. // SimpleFOC is supposed to be able to determine this automatically (if you omit params to initFOC), but
  35. // it seems to have a bug (or I've misconfigured it) that gets both the offset and direction very wrong!
  36. // So this value is based on experimentation.
  37. // TODO: dig into SimpleFOC calibration and find/fix the issue
  38. // float zero_electric_offset = -0.6; // original proto
  39. //float zero_electric_offset = 0.4; // handheld 1
  40. // float zero_electric_offset = -0.8; // handheld 2
  41. // float zero_electric_offset = 2.93; //0.15; // 17mm test
  42. // float zero_electric_offset = 0.66; // 15mm handheld
  43. float zero_electric_offset = 7.34;
  44. Direction foc_direction = Direction::CW;
  45. motor.pole_pairs = 7;
  46. driver.voltage_power_supply = 5;
  47. driver.init();
  48. #if SENSOR_TLV
  49. encoder.init(Wire, false);
  50. #endif
  51. #if SENSOR_MT6701
  52. encoder.init();
  53. // motor.LPF_angle = LowPassFilter(0.05);
  54. #endif
  55. // motor.LPF_current_q = {0.01};
  56. motor.linkDriver(&driver);
  57. motor.controller = MotionControlType::torque;
  58. motor.voltage_limit = 5;
  59. motor.velocity_limit = 10000;
  60. motor.linkSensor(&encoder);
  61. // Not actually using the velocity loop; but I'm using those PID variables
  62. // because SimpleFOC studio supports updating them easily over serial for tuning.
  63. motor.PID_velocity.P = 4;
  64. motor.PID_velocity.I = 0;
  65. motor.PID_velocity.D = 0.04;
  66. motor.PID_velocity.output_ramp = 10000;
  67. motor.PID_velocity.limit = 10;
  68. // motor.useMonitoring(Serial);
  69. motor.init();
  70. encoder.update();
  71. delay(10);
  72. motor.initFOC(zero_electric_offset, foc_direction);
  73. bool calibrate = false;
  74. Serial.println("Press Y to run calibration");
  75. uint32_t t = millis();
  76. while (millis() - t < 3000) {
  77. if (Serial.read() == 'Y') {
  78. calibrate = true;
  79. break;
  80. }
  81. delay(10);
  82. }
  83. if (calibrate) {
  84. motor.controller = MotionControlType::angle_openloop;
  85. motor.pole_pairs = 1;
  86. motor.initFOC(0, Direction::CW);
  87. float a = 0;
  88. for (uint8_t i = 0; i < 200; i++) {
  89. encoder.update();
  90. motor.move(a);
  91. delay(1);
  92. }
  93. float start_sensor = encoder.getAngle();
  94. for (; a < 3 * _2PI; a += 0.01) {
  95. encoder.update();
  96. motor.move(a);
  97. delay(1);
  98. }
  99. for (uint8_t i = 0; i < 200; i++) {
  100. encoder.update();
  101. delay(1);
  102. }
  103. float end_sensor = encoder.getAngle();
  104. motor.voltage_limit = 0;
  105. motor.move(a);
  106. // Serial.println("Did motor turn counterclockwise? Press Y to continue, otherwise change motor wiring and restart");
  107. // while (Serial.read() != 'Y') {
  108. // delay(10);
  109. // }
  110. Serial.println();
  111. // TODO: check for no motor movement!
  112. Serial.print("Sensor measures positive for positive motor rotation: ");
  113. if (end_sensor > start_sensor) {
  114. Serial.println("YES, Direction=CW");
  115. motor.initFOC(0, Direction::CW);
  116. } else {
  117. Serial.println("NO, Direction=CCW");
  118. motor.initFOC(0, Direction::CCW);
  119. }
  120. // Rotate many electrical revolutions and measure mechanical angle traveled, to calculate pole-pairs
  121. uint8_t electrical_revolutions = 20;
  122. Serial.printf("Going to measure %d electrical revolutions...\n", electrical_revolutions);
  123. motor.voltage_limit = 5;
  124. motor.move(a);
  125. Serial.println("Going to electrical zero...");
  126. float destination = a + _2PI;
  127. for (; a < destination; a += 0.03) {
  128. encoder.update();
  129. motor.move(a);
  130. delay(1);
  131. }
  132. Serial.println("pause...");
  133. for (uint16_t i = 0; i < 1000; i++) {
  134. encoder.update();
  135. delay(1);
  136. }
  137. Serial.println("Measuring...");
  138. start_sensor = motor.sensor_direction * encoder.getAngle();
  139. destination = a + electrical_revolutions * _2PI;
  140. for (; a < destination; a += 0.03) {
  141. encoder.update();
  142. motor.move(a);
  143. delay(1);
  144. }
  145. for (uint16_t i = 0; i < 1000; i++) {
  146. encoder.update();
  147. motor.move(a);
  148. delay(1);
  149. }
  150. end_sensor = motor.sensor_direction * encoder.getAngle();
  151. motor.voltage_limit = 0;
  152. motor.move(a);
  153. if (fabsf(motor.shaft_angle - motor.target) > 1 * PI / 180) {
  154. Serial.println("ERROR: motor did not reach target!");
  155. while(1) {}
  156. }
  157. float electrical_per_mechanical = electrical_revolutions * _2PI / (end_sensor - start_sensor);
  158. Serial.print("Electrical angle / mechanical angle (i.e. pole pairs) = ");
  159. Serial.println(electrical_per_mechanical);
  160. int measured_pole_pairs = (int)round(electrical_per_mechanical);
  161. Serial.printf("Pole pairs set to %d\n", measured_pole_pairs);
  162. delay(1000);
  163. // Measure mechanical angle at every electrical zero for several revolutions
  164. motor.voltage_limit = 5;
  165. motor.move(a);
  166. float offset_x = 0;
  167. float offset_y = 0;
  168. float destination1 = (floor(a / _2PI) + measured_pole_pairs / 2.) * _2PI;
  169. float destination2 = (floor(a / _2PI)) * _2PI;
  170. for (; a < destination1; a += 0.4) {
  171. motor.move(a);
  172. delay(100);
  173. for (uint8_t i = 0; i < 100; i++) {
  174. encoder.update();
  175. delay(1);
  176. }
  177. float real_electrical_angle = _normalizeAngle(a);
  178. float measured_electrical_angle = _normalizeAngle( (float)(motor.sensor_direction * measured_pole_pairs) * encoder.getMechanicalAngle() - 0);
  179. float offset_angle = measured_electrical_angle - real_electrical_angle;
  180. offset_x += cosf(offset_angle);
  181. offset_y += sinf(offset_angle);
  182. Serial.print(degrees(real_electrical_angle));
  183. Serial.print(", ");
  184. Serial.print(degrees(measured_electrical_angle));
  185. Serial.print(", ");
  186. Serial.println(degrees(_normalizeAngle(offset_angle)));
  187. }
  188. for (; a > destination2; a -= 0.4) {
  189. motor.move(a);
  190. delay(100);
  191. for (uint8_t i = 0; i < 100; i++) {
  192. encoder.update();
  193. delay(1);
  194. }
  195. float real_electrical_angle = _normalizeAngle(a);
  196. float measured_electrical_angle = _normalizeAngle( (float)(motor.sensor_direction * measured_pole_pairs) * encoder.getMechanicalAngle() - 0);
  197. float offset_angle = measured_electrical_angle - real_electrical_angle;
  198. offset_x += cosf(offset_angle);
  199. offset_y += sinf(offset_angle);
  200. Serial.print(degrees(real_electrical_angle));
  201. Serial.print(", ");
  202. Serial.print(degrees(measured_electrical_angle));
  203. Serial.print(", ");
  204. Serial.println(degrees(_normalizeAngle(offset_angle)));
  205. }
  206. motor.voltage_limit = 0;
  207. motor.move(a);
  208. float avg_offset_angle = atan2f(offset_y, offset_x);
  209. // Apply settings
  210. motor.pole_pairs = measured_pole_pairs;
  211. motor.zero_electric_angle = avg_offset_angle + _3PI_2;
  212. motor.voltage_limit = 5;
  213. motor.controller = MotionControlType::torque;
  214. Serial.print("\n\nRESULTS:\n zero electric angle: ");
  215. Serial.println(motor.zero_electric_angle);
  216. Serial.print(" direction: ");
  217. if (motor.sensor_direction == Direction::CW) {
  218. Serial.println("CW");
  219. } else {
  220. Serial.println("CCW");
  221. }
  222. Serial.printf(" pole pairs: %d\n", motor.pole_pairs);
  223. delay(2000);
  224. }
  225. Serial.println(motor.zero_electric_angle);
  226. command.add('M', &doMotor, "foo");
  227. // command.add('D', &doDetents, "Detents");
  228. motor.monitor_downsample = 0; // disable monitor at first - optional
  229. // disableCore0WDT();
  230. float current_detent_center = motor.shaft_angle;
  231. KnobConfig config = {
  232. .num_positions = 2,
  233. .position = 0,
  234. .position_width_radians = 60 * _PI / 180,
  235. .detent_strength_unit = 0,
  236. };
  237. float idle_check_velocity_ewma = 0;
  238. uint32_t last_idle_start = 0;
  239. uint32_t last_debug = 0;
  240. uint32_t last_publish = 0;
  241. while (1) {
  242. motor.loopFOC();
  243. Command command;
  244. if (xQueueReceive(queue_, &command, 0) == pdTRUE) {
  245. switch (command.command_type) {
  246. case CommandType::CONFIG: {
  247. config = command.data.config;
  248. Serial.println("Got new config");
  249. current_detent_center = motor.shaft_angle;
  250. #if SK_INVERT_ROTATION
  251. current_detent_center = -motor.shaft_angle;
  252. #endif
  253. // Update derivative factor of torque controller based on detent width.
  254. // If the D factor is large on coarse detents, the motor ends up making noise because the P&D factors amplify the noise from the sensor.
  255. // This is a piecewise linear function so that fine detents (small width) get a higher D factor and coarse detents get a small D factor.
  256. // Fine detents need a nonzero D factor to artificially create "clicks" each time a new value is reached (the P factor is small
  257. // for fine detents due to the smaller angular errors, and the existing P factor doesn't work well for very small angle changes (easy to
  258. // get runaway due to sensor noise & lag)).
  259. // TODO: consider eliminating this D factor entirely and just "play" a hardcoded haptic "click" (e.g. a quick burst of torque in each
  260. // direction) whenever the position changes when the detent width is too small for the P factor to work well.
  261. const float derivative_lower_strength = config.detent_strength_unit * 0.08;
  262. const float derivative_upper_strength = config.detent_strength_unit * 0.02;
  263. const float derivative_position_width_lower = radians(3);
  264. const float derivative_position_width_upper = radians(8);
  265. const float raw = derivative_lower_strength + (derivative_upper_strength - derivative_lower_strength)/(derivative_position_width_upper - derivative_position_width_lower)*(config.position_width_radians - derivative_position_width_lower);
  266. motor.PID_velocity.D = CLAMP(
  267. raw,
  268. min(derivative_lower_strength, derivative_upper_strength),
  269. max(derivative_lower_strength, derivative_upper_strength)
  270. );
  271. break;
  272. }
  273. case CommandType::HAPTIC: {
  274. float strength = command.data.haptic.press ? 5 : 1.5;
  275. motor.move(strength);
  276. for (uint8_t i = 0; i < 3; i++) {
  277. motor.loopFOC();
  278. delay(1);
  279. }
  280. motor.move(-strength);
  281. for (uint8_t i = 0; i < 3; i++) {
  282. motor.loopFOC();
  283. delay(1);
  284. }
  285. motor.move(0);
  286. motor.loopFOC();
  287. break;
  288. }
  289. }
  290. }
  291. idle_check_velocity_ewma = motor.shaft_velocity * IDLE_VELOCITY_EWMA_ALPHA + idle_check_velocity_ewma * (1 - IDLE_VELOCITY_EWMA_ALPHA);
  292. if (fabsf(idle_check_velocity_ewma) > IDLE_VELOCITY_RAD_PER_SEC) {
  293. last_idle_start = 0;
  294. } else {
  295. if (last_idle_start == 0) {
  296. last_idle_start = millis();
  297. }
  298. }
  299. // If we are not moving and we're close to the center (but not exactly there), slowly adjust the centerpoint to match the current position
  300. if (last_idle_start > 0 && millis() - last_idle_start > IDLE_CORRECTION_DELAY_MILLIS && fabsf(motor.shaft_angle - current_detent_center) < IDLE_CORRECTION_MAX_ANGLE_RAD) {
  301. current_detent_center = motor.shaft_angle * IDLE_CORRECTION_RATE_ALPHA + current_detent_center * (1 - IDLE_CORRECTION_RATE_ALPHA);
  302. // if (millis() - last_debug > 100) {
  303. // last_debug = millis();
  304. // Serial.print("Moving detent center. ");
  305. // Serial.print(current_detent_center);
  306. // Serial.print(" ");
  307. // Serial.println(motor.shaft_angle);
  308. // }
  309. }
  310. float angle_to_detent_center = motor.shaft_angle - current_detent_center;
  311. #if SK_INVERT_ROTATION
  312. angle_to_detent_center = -motor.shaft_angle - current_detent_center;
  313. #endif
  314. if (angle_to_detent_center > config.position_width_radians * config.snap_point && (config.num_positions <= 0 || config.position > 0)) {
  315. current_detent_center += config.position_width_radians;
  316. angle_to_detent_center -= config.position_width_radians;
  317. config.position--;
  318. } else if (angle_to_detent_center < -config.position_width_radians * config.snap_point && (config.num_positions <= 0 || config.position < config.num_positions - 1)) {
  319. current_detent_center -= config.position_width_radians;
  320. angle_to_detent_center += config.position_width_radians;
  321. config.position++;
  322. }
  323. float dead_zone_adjustment = CLAMP(
  324. angle_to_detent_center,
  325. fmaxf(-config.position_width_radians*DEAD_ZONE_DETENT_PERCENT, -DEAD_ZONE_RAD),
  326. fminf(config.position_width_radians*DEAD_ZONE_DETENT_PERCENT, DEAD_ZONE_RAD));
  327. bool out_of_bounds = config.num_positions > 0 && ((angle_to_detent_center > 0 && config.position == 0) || (angle_to_detent_center < 0 && config.position == config.num_positions - 1));
  328. motor.PID_velocity.limit = 10; //out_of_bounds ? 10 : 3;
  329. motor.PID_velocity.P = out_of_bounds ? config.endstop_strength_unit * 4 : config.detent_strength_unit * 4;
  330. if (fabsf(motor.shaft_velocity) > 60) {
  331. // Don't apply torque if velocity is too high (helps avoid positive feedback loop/runaway)
  332. motor.move(0);
  333. } else {
  334. float torque = motor.PID_velocity(-angle_to_detent_center + dead_zone_adjustment);
  335. #if SK_INVERT_ROTATION
  336. torque = -torque;
  337. #endif
  338. motor.move(torque);
  339. }
  340. if (millis() - last_publish > 10) {
  341. publish({
  342. .current_position = config.position,
  343. .sub_position_unit = -angle_to_detent_center / config.position_width_radians,
  344. .config = config,
  345. });
  346. last_publish = millis();
  347. }
  348. motor.monitor();
  349. // command.run();
  350. delay(1);
  351. }
  352. }
  353. void MotorTask::setConfig(const KnobConfig& config) {
  354. Command command = {
  355. .command_type = CommandType::CONFIG,
  356. .data = {
  357. .config = config,
  358. }
  359. };
  360. xQueueSend(queue_, &command, portMAX_DELAY);
  361. }
  362. void MotorTask::playHaptic(bool press) {
  363. Command command = {
  364. .command_type = CommandType::HAPTIC,
  365. .data = {
  366. .haptic = {
  367. .press = press,
  368. },
  369. }
  370. };
  371. xQueueSend(queue_, &command, portMAX_DELAY);
  372. }
  373. void MotorTask::addListener(QueueHandle_t queue) {
  374. listeners_.push_back(queue);
  375. }
  376. void MotorTask::publish(const KnobState& state) {
  377. for (auto listener : listeners_) {
  378. xQueueOverwrite(listener, &state);
  379. }
  380. }