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
Diff: brushless_motor.cpp
- Revision:
- 10:e40d8724268a
- Child:
- 11:bfb73f083009
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/brushless_motor.cpp Tue Jan 15 09:03:57 2019 +0000
@@ -0,0 +1,223 @@
+// 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]];
+}