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]];
}