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-19
- Revision:
- 11:bfb73f083009
- Parent:
- 10:e40d8724268a
- Child:
- 12:d1d21a2941ef
File content as of revision 11:bfb73f083009:
// 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 (PinName iadc, PinName pwv, PinName pwi,
const uint16_t * lutptr, PinName px, PinName py, PinName pz, PortName pn, int port_bit_mask)
: Motor_I(iadc), maxV(pwv,PWM_PRESECALER_DEFAULT), maxI(pwi,PWM_PRESECALER_DEFAULT), H1(px), H2(py), H3(pz), OP(pn, port_bit_mask) // Constructor
{
// Constructor
OP = 0;
H1.mode (PullUp);
H2.mode (PullUp);
H3.mode (PullUp);
H1.rise (callback(this, &brushless_motor::Hall_change)); // Attach handler to the rising interruptIn edge
H1.fall (callback(this, &brushless_motor::Hall_change)); // Attach handler to the falling interruptIn edge
H2.rise (callback(this, &brushless_motor::Hall_change)); // Attach handler to the rising interruptIn edge
H2.fall (callback(this, &brushless_motor::Hall_change)); // Attach handler to the falling interruptIn edge
H3.rise (callback(this, &brushless_motor::Hall_change)); // Attach handler to the rising interruptIn edge
H3.fall (callback(this, &brushless_motor::Hall_change)); // Attach handler to the falling interruptIn edge
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;
Hall_tab_ptr = 0;
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;
Hall_index[0] = Hall_index[1] = read_Halls ();
ppstmp = 0;
cs_ptr = 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
PPS = 0;
RPM = 0;
last_V = last_I = 0.0;
dc_motor = (Hall_index[0] == 7) ? true : false ;
}
void brushless_motor::sniff_current () {
current_samples[cs_ptr++] = Motor_I.read_u16 (); //
if (cs_ptr >= CURRENT_SAMPLES_AVERAGED)
cs_ptr = 0;
}
void brushless_motor::Hall_change () {
int32_t delta_theta;
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
} ;
Hall_index[0] = read_Halls ();
delta_theta = delta_theta_lut[(Hall_index[1] << 3) | Hall_index[0]];
if (delta_theta == 0)
encoder_error_cnt++;
else
angle_cnt += delta_theta;
OP = lut[inner_mode | Hall_index[0]]; // changed mode to inner_mode 27/04/18
Hall_total++;
Hall_index[1] = Hall_index[0];
}
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 (H1 != 0) x |= 1;
if (H2 != 0) x |= 2;
if (H3 != 0) x |= 4;
return x;
}
void brushless_motor::high_side_off ()
{
// uint16_t p = *Motor_Port;
uint16_t p = OP;
p &= lut[32]; // KEEP_L_MASK_A or B
// *Motor_Port = p;
OP = 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];
OP = 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::motor_set ()
{
Hall_index[0] = read_Halls ();
// *Motor_Port = lut[inner_mode | Hindex[0]];
OP = lut[inner_mode | Hall_index[0]];
}