Dual Brushless Motor ESC, 10-62V, up to 50A per motor. Motors ganged or independent, multiple control input methods, cycle-by-cycle current limit, speed mode and torque mode control. Motors tiny to kW. Speed limit and other parameters easily set in firmware. As used in 'The Brushless Brutalist' locomotive - www.jons-workshop.com. See also Model Engineer magazine June-October 2019.
Dependencies: mbed BufferedSerial Servo PCT2075 FastPWM
Update 17th August 2020 Radio control inputs completed
brushless_motor.cpp
- Committer:
- JonFreeman
- Date:
- 2019-01-15
- Revision:
- 10:e40d8724268a
- Child:
- 11:bfb73f083009
File content as of revision 10:e40d8724268a:
// Cloned from 'DualBLS2018_06' on 23 November 2018 #include "mbed.h" //#include "users/mbed_official/code/mbed-dev/file/707f6e361f3e/targets/TARGET_STM/TARGET_STM32F4/TARGET_STM32F401xE/device/stm32f401xe.h" #include "stm32f401xe.h" #include "DualBLS.h" #include "BufferedSerial.h" #include "FastPWM.h" #include "Servo.h" #include "brushless_motor.h" brushless_motor::brushless_motor (PortOut * P , FastPWM * _maxV_ , FastPWM * _maxI_ , const uint16_t * lutptr, InterruptIn ** Hall) // Constructor { // Constructor maxV = _maxV_; maxI = _maxI_; Hall_total = 0; // mode can be only 0, 8, 16 or 24, lut row select for Handbrake, Forward, Reverse, or Regen Braking latest_pulses_per_sec = 0; for (int i = 0; i < MAIN_LOOP_ITERATION_Hz; i++) edge_count_table[i] = 0; // if (lutptr != A_tabl && lutptr != B_tabl) // pc.printf ("Fatal in 'motor' constructor, Invalid lut address\r\n"); Hall_tab_ptr = 0; Motor_Port = P; // pc.printf ("In motor constructor, Motor port = %lx\r\n", P); maxV->period_ticks (MAX_PWM_TICKS + 1); // around 18 kHz maxI->period_ticks (MAX_PWM_TICKS + 1); maxV->pulsewidth_ticks (MAX_PWM_TICKS / 20); maxI->pulsewidth_ticks (MAX_PWM_TICKS / 30); visible_mode = REGENBRAKE; inner_mode = REGENBRAKE; lut = lutptr; Hindex[0] = Hindex[1] = read_Halls (); ppstmp = 0; tickleon = 0; direction = 0; angle_cnt = 0; // Incremented or decremented on each Hall event according to actual measured direction of travel encoder_error_cnt = 0; // Incremented when Hall transition not recognised as either direction Hall1 = Hall[0]; Hall2 = Hall[1]; Hall3 = Hall[2]; PPS = 0; RPM = 0; last_V = last_I = 0.0; int x = read_Halls (); if (x == 7) dc_motor = true; else dc_motor = false; } bool brushless_motor::motor_is_brushless () { /* int x = read_Halls (); if (x < 1 || x > 6) return false; return true; */ return !dc_motor; } /** void brushless_motor::direction_set (int dir) { Used to set direction according to mode data from eeprom */ void brushless_motor::direction_set (int dir) { if (dir != 0) dir = FORWARD | REVERSE; // bits used in eor direction = dir; } int brushless_motor::read_Halls () { int x = 0; if (*Hall1 != 0) x |= 1; if (*Hall2 != 0) x |= 2; if (*Hall3 != 0) x |= 4; return x; } void brushless_motor::high_side_off () { uint16_t p = *Motor_Port; p &= lut[32]; // KEEP_L_MASK_A or B *Motor_Port = p; } void brushless_motor::low_side_on () { // uint16_t p = *Motor_Port; // p &= lut[31]; // KEEP_L_MASK_A or B *Motor_Port = lut[31]; } void brushless_motor::current_calc () { I.min = 0x0fffffff; // samples are 16 bit I.max = 0; I.ave = 0; uint16_t sample; for (int i = 0; i < CURRENT_SAMPLES_AVERAGED; i++) { sample = current_samples[i]; I.ave += sample; if (I.min > sample) I.min = sample; if (I.max < sample) I.max = sample; } I.ave /= CURRENT_SAMPLES_AVERAGED; } void brushless_motor::raw_V_pwm (int v) { if (v < 1) v = 1; if (v > MAX_PWM_TICKS) v = MAX_PWM_TICKS; maxV->pulsewidth_ticks (v); } void brushless_motor::set_V_limit (double p) // Sets max motor voltage. Use set_V_limit (last_V) to restore previous setting { if (p < 0.0) p = 0.0; if (p > 1.0) p = 1.0; last_V = p; // for read by diagnostics p *= 0.95; // need limit, ffi see MCP1630 data p = 1.0 - p; // because pwm is wrong way up maxV->pulsewidth_ticks ((int)(p * MAX_PWM_TICKS)); // PWM output to MCP1630 inverted motor pwm as MCP1630 inverts } void brushless_motor::set_I_limit (double p) // Sets max motor current. pwm integrated to dc ref voltage level { int a; if (p < 0.0) p = 0.0; if (p > 1.0) p = 1.0; last_I = p; a = (int)(p * MAX_PWM_TICKS); if (a > MAX_PWM_TICKS) a = MAX_PWM_TICKS; if (a < 0) a = 0; maxI->pulsewidth_ticks (a); // PWM } uint32_t brushless_motor::pulses_per_sec () // call this once per 'MAIN_LOOP_REPEAT_TIME_US= 31250' main loop pass to keep count = edges per sec { // Can also test for motor running or not here if (dc_motor) return 0; if (ppstmp == Hall_total) { // if (dc_motor || ppstmp == Hall_total) { moving_flag = false; // Zero Hall transitions since previous call - motor not moving tickleon = TICKLE_TIMES; } else { moving_flag = true; ppstmp = Hall_total; } latest_pulses_per_sec = ppstmp - edge_count_table[Hall_tab_ptr]; edge_count_table[Hall_tab_ptr] = ppstmp; Hall_tab_ptr++; if (Hall_tab_ptr >= MAIN_LOOP_ITERATION_Hz) Hall_tab_ptr = 0; PPS = latest_pulses_per_sec; RPM = (latest_pulses_per_sec * 60) / 24; return latest_pulses_per_sec; } bool brushless_motor::is_moving () { return moving_flag; } /** bool brushless_motor::set_mode (int m) Use to set motor to one mode of HANDBRAKE, FORWARD, REVERSE, REGENBRAKE. If this causes change of mode, also sets V and I to zero. */ bool brushless_motor::set_mode (int m) { if ((m != HANDBRAKE) && (m != FORWARD) && (m != REVERSE) && (m !=REGENBRAKE)) { // pc.printf ("Error in set_mode, invalid mode %d\r\n", m); return false; } if (visible_mode != m) { // Mode change, kill volts and amps to be safe set_V_limit (0.0); set_I_limit (0.0); visible_mode = m; } if (m == FORWARD || m == REVERSE) m ^= direction; inner_mode = m; // idea is to use inner_mode only in lut addressing, keep 'visible_mode' true regardless of setup data in eeprom return true; } void brushless_motor::Hall_change () { const int32_t delta_theta_lut[] = { // Looks up -1 for forward move detected, +1 for reverse move detected, 0 for error or unknown 0, 0, 0, 0, 0, 0, 0, 0, // Previous Hindex was 0 0, 0, 0,-1, 0, 1, 0, 0, // Previous Hindex was 1 0, 0, 0, 1, 0, 0,-1, 0, // Previous Hindex was 2 0, 1,-1, 0, 0, 0, 0, 0, // Previous Hindex was 3 0, 0, 0, 0, 0,-1, 1, 0, // Previous Hindex was 4 0,-1, 0, 0, 1, 0, 0, 0, // Previous Hindex was 5 0, 0, 1, 0,-1, 0, 0, 0, // Previous Hindex was 6 0, 0, 0, 0, 0, 0, 0, 0, // Previous Hindex was 7 } ; int32_t delta_theta = delta_theta_lut[(Hindex[1] << 3) | Hindex[0]]; if (delta_theta == 0) encoder_error_cnt++; else angle_cnt += delta_theta; *Motor_Port = lut[inner_mode | Hindex[0]]; // changed mode to inner_mode 27/04/18 Hall_total++; Hindex[1] = Hindex[0]; } void brushless_motor::motor_set () { Hindex[0] = read_Halls (); *Motor_Port = lut[inner_mode | Hindex[0]]; }