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@17:cc9b854295d6, 2020-08-16 (annotated)
- Committer:
- JonFreeman
- Date:
- Sun Aug 16 14:13:19 2020 +0000
- Revision:
- 17:cc9b854295d6
- Parent:
- 16:d1e4b9ad3b8b
August 2020. Checked Radio Control input ops.
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
JonFreeman | 14:acaa1add097b | 1 | /* |
JonFreeman | 14:acaa1add097b | 2 | STM3_ESC Electronic Speed Controller board, drives Two Brushless Motors, full Four Quadrant Control. |
JonFreeman | 14:acaa1add097b | 3 | Jon Freeman B. Eng Hons |
JonFreeman | 16:d1e4b9ad3b8b | 4 | 2015 - 2020 |
JonFreeman | 14:acaa1add097b | 5 | */ |
JonFreeman | 10:e40d8724268a | 6 | #include "mbed.h" |
JonFreeman | 10:e40d8724268a | 7 | //#include "users/mbed_official/code/mbed-dev/file/707f6e361f3e/targets/TARGET_STM/TARGET_STM32F4/TARGET_STM32F401xE/device/stm32f401xe.h" |
JonFreeman | 13:ef7a06fa11de | 8 | //#include "stm32f401xe.h" |
JonFreeman | 13:ef7a06fa11de | 9 | //#include "stm32f411xe.h" |
JonFreeman | 13:ef7a06fa11de | 10 | #include "STM3_ESC.h" |
JonFreeman | 10:e40d8724268a | 11 | #include "BufferedSerial.h" |
JonFreeman | 10:e40d8724268a | 12 | #include "brushless_motor.h" |
JonFreeman | 11:bfb73f083009 | 13 | |
JonFreeman | 16:d1e4b9ad3b8b | 14 | extern eeprom_settings user_settings ; |
JonFreeman | 16:d1e4b9ad3b8b | 15 | //extern BufferedSerial pc; // The two com ports used. There is also an unused com port, com3 setup @ 1200 baud |
JonFreeman | 11:bfb73f083009 | 16 | |
JonFreeman | 16:d1e4b9ad3b8b | 17 | /** |
JonFreeman | 11:bfb73f083009 | 18 | brushless_motor::brushless_motor (PinName iadc, PinName pwv, PinName pwi, |
JonFreeman | 12:d1d21a2941ef | 19 | const uint16_t * lutptr, PinName px, PinName py, PinName pz, PortName pn, int port_bit_mask, uint32_t rnum) |
JonFreeman | 11:bfb73f083009 | 20 | : 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 |
JonFreeman | 16:d1e4b9ad3b8b | 21 | */ |
JonFreeman | 16:d1e4b9ad3b8b | 22 | brushless_motor::brushless_motor (PinName iadc, PinName pwv, PinName pwi, |
JonFreeman | 17:cc9b854295d6 | 23 | const uint16_t * lutptr, PinName px, PinName py, PinName pz, PortName pn, const uint16_t port_bit_mask, const uint32_t rnum) |
JonFreeman | 16:d1e4b9ad3b8b | 24 | : 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 |
JonFreeman | 16:d1e4b9ad3b8b | 25 | { // Constructor |
JonFreeman | 11:bfb73f083009 | 26 | OP = 0; |
JonFreeman | 12:d1d21a2941ef | 27 | H1.mode (PullUp); // PullUp resistors enabled on all Hall sensor input pins |
JonFreeman | 11:bfb73f083009 | 28 | H2.mode (PullUp); |
JonFreeman | 11:bfb73f083009 | 29 | H3.mode (PullUp); |
JonFreeman | 11:bfb73f083009 | 30 | H1.rise (callback(this, &brushless_motor::Hall_change)); // Attach handler to the rising interruptIn edge |
JonFreeman | 11:bfb73f083009 | 31 | H1.fall (callback(this, &brushless_motor::Hall_change)); // Attach handler to the falling interruptIn edge |
JonFreeman | 11:bfb73f083009 | 32 | H2.rise (callback(this, &brushless_motor::Hall_change)); // Attach handler to the rising interruptIn edge |
JonFreeman | 11:bfb73f083009 | 33 | H2.fall (callback(this, &brushless_motor::Hall_change)); // Attach handler to the falling interruptIn edge |
JonFreeman | 11:bfb73f083009 | 34 | H3.rise (callback(this, &brushless_motor::Hall_change)); // Attach handler to the rising interruptIn edge |
JonFreeman | 11:bfb73f083009 | 35 | H3.fall (callback(this, &brushless_motor::Hall_change)); // Attach handler to the falling interruptIn edge |
JonFreeman | 10:e40d8724268a | 36 | Hall_total = 0; // mode can be only 0, 8, 16 or 24, lut row select for Handbrake, Forward, Reverse, or Regen Braking |
JonFreeman | 12:d1d21a2941ef | 37 | Hall_previous = 0; |
JonFreeman | 11:bfb73f083009 | 38 | maxV.period_ticks (MAX_PWM_TICKS + 1); // around 18 kHz |
JonFreeman | 11:bfb73f083009 | 39 | maxI.period_ticks (MAX_PWM_TICKS + 1); |
JonFreeman | 12:d1d21a2941ef | 40 | maxV.pulsewidth_ticks (MAX_PWM_TICKS - 20); // Motor voltage pwm is inverted, see MCP1630 data |
JonFreeman | 12:d1d21a2941ef | 41 | maxI.pulsewidth_ticks (MAX_PWM_TICKS / 30); // Motor current pwm is not inverted. Initial values for scope hanging test |
JonFreeman | 12:d1d21a2941ef | 42 | visible_mode = MOTOR_REGENBRAKE; |
JonFreeman | 12:d1d21a2941ef | 43 | inner_mode = MOTOR_REGENBRAKE; |
JonFreeman | 12:d1d21a2941ef | 44 | lut = lutptr; // Pointer to motor energisation bit pattern table |
JonFreeman | 16:d1e4b9ad3b8b | 45 | current_sense_rs_offset = rnum; // This is position in user_settings.rd(current_sense_rs_offset) |
JonFreeman | 11:bfb73f083009 | 46 | Hall_index[0] = Hall_index[1] = read_Halls (); |
JonFreeman | 10:e40d8724268a | 47 | tickleon = 0; |
JonFreeman | 10:e40d8724268a | 48 | direction = 0; |
JonFreeman | 10:e40d8724268a | 49 | angle_cnt = 0; // Incremented or decremented on each Hall event according to actual measured direction of travel |
JonFreeman | 10:e40d8724268a | 50 | encoder_error_cnt = 0; // Incremented when Hall transition not recognised as either direction |
JonFreeman | 10:e40d8724268a | 51 | last_V = last_I = 0.0; |
JonFreeman | 12:d1d21a2941ef | 52 | Idbl = 0.0; |
JonFreeman | 16:d1e4b9ad3b8b | 53 | // numof_current_sense_rs = 1.0; |
JonFreeman | 12:d1d21a2941ef | 54 | RPM_filter = 0.0; |
JonFreeman | 12:d1d21a2941ef | 55 | dRPM = 0.0; |
JonFreeman | 12:d1d21a2941ef | 56 | V_clamp = 1.0 ; // Used to limit top speed |
JonFreeman | 12:d1d21a2941ef | 57 | motor_poles = 8; // Default to 8 pole motor |
JonFreeman | 11:bfb73f083009 | 58 | } |
JonFreeman | 11:bfb73f083009 | 59 | |
JonFreeman | 12:d1d21a2941ef | 60 | /** |
JonFreeman | 14:acaa1add097b | 61 | * void brushless_motor::sniff_current () { // Initiate ADC current reading of approx motor average current |
JonFreeman | 12:d1d21a2941ef | 62 | * This to be called in response to ticker timebase interrupt. |
JonFreeman | 12:d1d21a2941ef | 63 | * As designed, called at 200 micro second intervals (Feb 2019) |
JonFreeman | 12:d1d21a2941ef | 64 | * Updates double I.dbl current measured in milliamps |
JonFreeman | 12:d1d21a2941ef | 65 | * Reading not used elsewhere in this code but made available through command line for external controller |
JonFreeman | 12:d1d21a2941ef | 66 | */ |
JonFreeman | 12:d1d21a2941ef | 67 | void brushless_motor::sniff_current () { // Initiate ADC current reading |
JonFreeman | 12:d1d21a2941ef | 68 | const double sampweight = 0.01 ; /// (double)CURRENT_SAMPLES_AVERAGED ; |
JonFreeman | 12:d1d21a2941ef | 69 | const double shrinkby = 1.0 - sampweight; |
JonFreeman | 12:d1d21a2941ef | 70 | uint16_t samp = Motor_I.read_u16 (); // CHECK but thought to be called once per 200us for each motor - CORRECT Feb 2019 |
JonFreeman | 16:d1e4b9ad3b8b | 71 | // double dbls = ((double)samp) * numof_current_sense_rs / 6.0; // reading in mA |
JonFreeman | 16:d1e4b9ad3b8b | 72 | // double dbls = (((double)samp) / 6.0) * (double)user_settings.rd(current_sense_rs_offset); // reading in mA |
JonFreeman | 16:d1e4b9ad3b8b | 73 | // double dbls = sampweight * (double)(samp * user_settings.rd(current_sense_rs_offset)) / 6.0; // reading in mA |
JonFreeman | 12:d1d21a2941ef | 74 | Idbl *= shrinkby; // Jan 2019 New recursive low pass filter |
JonFreeman | 16:d1e4b9ad3b8b | 75 | // Idbl += dbls * sampweight; // Current reading available, however not certain this is of any real use |
JonFreeman | 16:d1e4b9ad3b8b | 76 | Idbl += sampweight * (double)(samp * user_settings.rd(current_sense_rs_offset)) / 6.0; // Current reading available, however not certain this is of any real use |
JonFreeman | 12:d1d21a2941ef | 77 | } |
JonFreeman | 12:d1d21a2941ef | 78 | |
JonFreeman | 16:d1e4b9ad3b8b | 79 | bool brushless_motor::poles (uint32_t p) { |
JonFreeman | 12:d1d21a2941ef | 80 | if (p == 4 || p == 6 || p == 8) { |
JonFreeman | 12:d1d21a2941ef | 81 | motor_poles = p; |
JonFreeman | 12:d1d21a2941ef | 82 | return true; |
JonFreeman | 12:d1d21a2941ef | 83 | } |
JonFreeman | 12:d1d21a2941ef | 84 | return false; |
JonFreeman | 11:bfb73f083009 | 85 | } |
JonFreeman | 11:bfb73f083009 | 86 | |
JonFreeman | 11:bfb73f083009 | 87 | void brushless_motor::Hall_change () { |
JonFreeman | 11:bfb73f083009 | 88 | int32_t delta_theta; |
JonFreeman | 11:bfb73f083009 | 89 | const int32_t delta_theta_lut[] = { // Looks up -1 for forward move detected, +1 for reverse move detected, 0 for error or unknown |
JonFreeman | 11:bfb73f083009 | 90 | 0, 0, 0, 0, 0, 0, 0, 0, // Previous Hindex was 0 |
JonFreeman | 11:bfb73f083009 | 91 | 0, 0, 0,-1, 0, 1, 0, 0, // Previous Hindex was 1 |
JonFreeman | 11:bfb73f083009 | 92 | 0, 0, 0, 1, 0, 0,-1, 0, // Previous Hindex was 2 |
JonFreeman | 11:bfb73f083009 | 93 | 0, 1,-1, 0, 0, 0, 0, 0, // Previous Hindex was 3 |
JonFreeman | 11:bfb73f083009 | 94 | 0, 0, 0, 0, 0,-1, 1, 0, // Previous Hindex was 4 |
JonFreeman | 11:bfb73f083009 | 95 | 0,-1, 0, 0, 1, 0, 0, 0, // Previous Hindex was 5 |
JonFreeman | 11:bfb73f083009 | 96 | 0, 0, 1, 0,-1, 0, 0, 0, // Previous Hindex was 6 |
JonFreeman | 11:bfb73f083009 | 97 | 0, 0, 0, 0, 0, 0, 0, 0, // Previous Hindex was 7 |
JonFreeman | 11:bfb73f083009 | 98 | } ; |
JonFreeman | 11:bfb73f083009 | 99 | |
JonFreeman | 11:bfb73f083009 | 100 | Hall_index[0] = read_Halls (); |
JonFreeman | 11:bfb73f083009 | 101 | delta_theta = delta_theta_lut[(Hall_index[1] << 3) | Hall_index[0]]; |
JonFreeman | 11:bfb73f083009 | 102 | |
JonFreeman | 16:d1e4b9ad3b8b | 103 | if (delta_theta == 0) // Should only ever be +1 in 1 direction, -1 in other direction. 0 indicates invalid Hall sensor transition detected. |
JonFreeman | 16:d1e4b9ad3b8b | 104 | encoder_error_cnt++; // Never used Dec 2019 |
JonFreeman | 10:e40d8724268a | 105 | else |
JonFreeman | 11:bfb73f083009 | 106 | angle_cnt += delta_theta; |
JonFreeman | 11:bfb73f083009 | 107 | OP = lut[inner_mode | Hall_index[0]]; // changed mode to inner_mode 27/04/18 |
JonFreeman | 11:bfb73f083009 | 108 | Hall_total++; |
JonFreeman | 11:bfb73f083009 | 109 | Hall_index[1] = Hall_index[0]; |
JonFreeman | 10:e40d8724268a | 110 | } |
JonFreeman | 10:e40d8724268a | 111 | |
JonFreeman | 10:e40d8724268a | 112 | /** |
JonFreeman | 16:d1e4b9ad3b8b | 113 | * void brushless_motor::set_direction (int dir) { |
JonFreeman | 16:d1e4b9ad3b8b | 114 | * Used to set direction according to user_settings data from eeprom |
JonFreeman | 10:e40d8724268a | 115 | */ |
JonFreeman | 16:d1e4b9ad3b8b | 116 | void brushless_motor::set_direction (uint32_t dir) |
JonFreeman | 10:e40d8724268a | 117 | { |
JonFreeman | 12:d1d21a2941ef | 118 | direction = (dir != 0) ? MOTOR_FORWARD | MOTOR_REVERSE : 0; // bits used in eor, FORWARD = 1<<3, REVERSE = 1<<4 |
JonFreeman | 10:e40d8724268a | 119 | } |
JonFreeman | 10:e40d8724268a | 120 | |
JonFreeman | 16:d1e4b9ad3b8b | 121 | //uint32_t get_direction () { |
JonFreeman | 16:d1e4b9ad3b8b | 122 | // return direction; |
JonFreeman | 16:d1e4b9ad3b8b | 123 | //} |
JonFreeman | 16:d1e4b9ad3b8b | 124 | |
JonFreeman | 16:d1e4b9ad3b8b | 125 | uint32_t brushless_motor::read_Halls () |
JonFreeman | 10:e40d8724268a | 126 | { |
JonFreeman | 16:d1e4b9ad3b8b | 127 | uint32_t x = 0; |
JonFreeman | 12:d1d21a2941ef | 128 | if (H1) x |= 1; |
JonFreeman | 12:d1d21a2941ef | 129 | if (H2) x |= 2; |
JonFreeman | 12:d1d21a2941ef | 130 | if (H3) x |= 4; |
JonFreeman | 10:e40d8724268a | 131 | return x; |
JonFreeman | 10:e40d8724268a | 132 | } |
JonFreeman | 10:e40d8724268a | 133 | |
JonFreeman | 12:d1d21a2941ef | 134 | void brushless_motor::high_side_off () // Jan 2019 Only ever called from main when high side gate drive charge might need pumping up |
JonFreeman | 10:e40d8724268a | 135 | { |
JonFreeman | 11:bfb73f083009 | 136 | uint16_t p = OP; |
JonFreeman | 10:e40d8724268a | 137 | p &= lut[32]; // KEEP_L_MASK_A or B |
JonFreeman | 11:bfb73f083009 | 138 | OP = p; |
JonFreeman | 10:e40d8724268a | 139 | } |
JonFreeman | 12:d1d21a2941ef | 140 | /* |
JonFreeman | 10:e40d8724268a | 141 | void brushless_motor::low_side_on () |
JonFreeman | 10:e40d8724268a | 142 | { |
JonFreeman | 12:d1d21a2941ef | 143 | maxV.pulsewidth_ticks (1); |
JonFreeman | 12:d1d21a2941ef | 144 | OP = lut[31]; // KEEP_L_MASK_A or B |
JonFreeman | 10:e40d8724268a | 145 | } |
JonFreeman | 12:d1d21a2941ef | 146 | */ |
JonFreeman | 10:e40d8724268a | 147 | |
JonFreeman | 10:e40d8724268a | 148 | |
JonFreeman | 13:ef7a06fa11de | 149 | extern int WatchDog; |
JonFreeman | 13:ef7a06fa11de | 150 | #define DRIVING (visible_mode == MOTOR_FORWARD || visible_mode == MOTOR_REVERSE) |
JonFreeman | 13:ef7a06fa11de | 151 | #define ESTOP (WatchDog == 0 && DRIVING) |
JonFreeman | 13:ef7a06fa11de | 152 | |
JonFreeman | 12:d1d21a2941ef | 153 | /** |
JonFreeman | 12:d1d21a2941ef | 154 | * void brushless_motor::set_V_limit (double p) // Sets max motor voltage. |
JonFreeman | 12:d1d21a2941ef | 155 | * |
JonFreeman | 12:d1d21a2941ef | 156 | * Set motor voltage limit from zero (p=0.0) to max link voltage (p=1.0) |
JonFreeman | 12:d1d21a2941ef | 157 | */ |
JonFreeman | 16:d1e4b9ad3b8b | 158 | void brushless_motor::motor_voltage_refresh () // May alter motor voltage to reflect changes in V_clamp |
JonFreeman | 16:d1e4b9ad3b8b | 159 | { |
JonFreeman | 16:d1e4b9ad3b8b | 160 | double p = last_V; |
JonFreeman | 16:d1e4b9ad3b8b | 161 | if ((V_clamp < last_V) && DRIVING) // Jan 2019 limit top speed when driving |
JonFreeman | 16:d1e4b9ad3b8b | 162 | p = V_clamp; // If motor runnable, set voltage limit to lower of last_V and V_clamp |
JonFreeman | 16:d1e4b9ad3b8b | 163 | |
JonFreeman | 16:d1e4b9ad3b8b | 164 | p *= 0.95; // need limit, ffi see MCP1630 data |
JonFreeman | 16:d1e4b9ad3b8b | 165 | p = 1.0 - p; // because pwm is wrong way up |
JonFreeman | 16:d1e4b9ad3b8b | 166 | maxV.pulsewidth_ticks ((int)(p * MAX_PWM_TICKS)); // PWM output to MCP1630 inverted motor pwm as MCP1630 inverts |
JonFreeman | 16:d1e4b9ad3b8b | 167 | } |
JonFreeman | 16:d1e4b9ad3b8b | 168 | |
JonFreeman | 12:d1d21a2941ef | 169 | void brushless_motor::set_V_limit (double p) // Sets max motor voltage. |
JonFreeman | 10:e40d8724268a | 170 | { |
JonFreeman | 13:ef7a06fa11de | 171 | if (p < 0.0 || ESTOP) |
JonFreeman | 10:e40d8724268a | 172 | p = 0.0; |
JonFreeman | 10:e40d8724268a | 173 | if (p > 1.0) |
JonFreeman | 10:e40d8724268a | 174 | p = 1.0; |
JonFreeman | 12:d1d21a2941ef | 175 | last_V = p; // Retains last voltage limit demanded by driver |
JonFreeman | 16:d1e4b9ad3b8b | 176 | motor_voltage_refresh () ; |
JonFreeman | 10:e40d8724268a | 177 | } |
JonFreeman | 10:e40d8724268a | 178 | |
JonFreeman | 13:ef7a06fa11de | 179 | |
JonFreeman | 12:d1d21a2941ef | 180 | /**void brushless_motor::set_I_limit (double p) // Sets max motor current. pwm integrated to dc ref voltage level |
JonFreeman | 12:d1d21a2941ef | 181 | * |
JonFreeman | 12:d1d21a2941ef | 182 | * Set motor current limit from zero (p=0.0) to max determined by current sense resistors (p=1.0) |
JonFreeman | 12:d1d21a2941ef | 183 | * Value sent to pwm with RC integrator acting as AnalogOut. |
JonFreeman | 12:d1d21a2941ef | 184 | * pwm capable of 0.0 <= V_out <= 3.3. This feeds MCP1630 V_Ref, range 0 to 2.7v |
JonFreeman | 12:d1d21a2941ef | 185 | * Therefore (2.7/3.3) = 0.82 factor included. |
JonFreeman | 12:d1d21a2941ef | 186 | * Jan 2019 - As yet uncalibrated, so let's have a go at working it out! |
JonFreeman | 12:d1d21a2941ef | 187 | * Voltage ax current sense resistors amplified by op-amp with gain 5.7 (see EasyPC file 'BrushlessSTM3.sch', U6, R63, R64) |
JonFreeman | 12:d1d21a2941ef | 188 | * This then put through pot divider (10k with 4k7 to ground, gain 0.32) reducing overall gain to 1.8 (accurate enough) |
JonFreeman | 12:d1d21a2941ef | 189 | * This connects to MCP1630 CS (current sense) pin which works over the voltage range 0.0 to 0.9v |
JonFreeman | 12:d1d21a2941ef | 190 | * Therefore 0.5v across current sense resistor is sufficient to turn driver off. |
JonFreeman | 12:d1d21a2941ef | 191 | * 0.5v across 0.05 ohm gives 10A per current sense resistor fitted. |
JonFreeman | 12:d1d21a2941ef | 192 | * ** NOTE ** This is fast acting cycle by cycle current limit, the 10A figure is therefore peak T_on current. |
JonFreeman | 12:d1d21a2941ef | 193 | * |
JonFreeman | 12:d1d21a2941ef | 194 | * Current flows through current sense reaistor when one high side and one low side switch are on, expect a rising ramp due to motor inductance. |
JonFreeman | 12:d1d21a2941ef | 195 | * When either switch is off, inductor current continues to flow but not through current sense resistors, through a parasitic diode instead. |
JonFreeman | 12:d1d21a2941ef | 196 | * Thus T_on current is measured, T_off current is not measured |
JonFreeman | 12:d1d21a2941ef | 197 | * This means current reading should approximate to current taken from the supply. Motor average current may be considerably higher. |
JonFreeman | 12:d1d21a2941ef | 198 | * During REGEN_BRAKING, current flows the 'wrong' way through sense resistors and can not be measured. |
JonFreeman | 12:d1d21a2941ef | 199 | * |
JonFreeman | 12:d1d21a2941ef | 200 | * Board designed to have 1, 2, 3 or 4 0R05 current sense resistors per motor for 10A, 20A, 30A or 40A peak motor currents |
JonFreeman | 12:d1d21a2941ef | 201 | */ |
JonFreeman | 16:d1e4b9ad3b8b | 202 | //const double MPR = (double)((MAX_PWM_TICKS * 9) / 11); // Scales 3.3v pwm DAC output to 2.7v V_Ref input |
JonFreeman | 16:d1e4b9ad3b8b | 203 | const uint32_t MPR = ((MAX_PWM_TICKS * 9) / 11); // Scales 3.3v pwm DAC output to 2.7v V_Ref input |
JonFreeman | 13:ef7a06fa11de | 204 | |
JonFreeman | 10:e40d8724268a | 205 | void brushless_motor::set_I_limit (double p) // Sets max motor current. pwm integrated to dc ref voltage level |
JonFreeman | 10:e40d8724268a | 206 | { |
JonFreeman | 13:ef7a06fa11de | 207 | if (p < 0.0 || ESTOP) |
JonFreeman | 10:e40d8724268a | 208 | p = 0.0; |
JonFreeman | 10:e40d8724268a | 209 | if (p > 1.0) |
JonFreeman | 10:e40d8724268a | 210 | p = 1.0; |
JonFreeman | 12:d1d21a2941ef | 211 | last_I = p; // Retains last current limit demanded by driver |
JonFreeman | 13:ef7a06fa11de | 212 | if (DRIVING) { |
JonFreeman | 16:d1e4b9ad3b8b | 213 | p *= current_scale; |
JonFreeman | 13:ef7a06fa11de | 214 | } |
JonFreeman | 12:d1d21a2941ef | 215 | maxI.pulsewidth_ticks ((uint32_t)(p * MPR)); // PWM |
JonFreeman | 10:e40d8724268a | 216 | } |
JonFreeman | 10:e40d8724268a | 217 | |
JonFreeman | 16:d1e4b9ad3b8b | 218 | void brushless_motor::I_scale (double p) // Sets max motor current. pwm integrated to dc ref voltage level |
JonFreeman | 16:d1e4b9ad3b8b | 219 | { |
JonFreeman | 16:d1e4b9ad3b8b | 220 | current_scale = p; |
JonFreeman | 16:d1e4b9ad3b8b | 221 | if (DRIVING) { |
JonFreeman | 16:d1e4b9ad3b8b | 222 | maxI.pulsewidth_ticks ((uint32_t)(last_I * p * MPR)); // PWM |
JonFreeman | 16:d1e4b9ad3b8b | 223 | } |
JonFreeman | 16:d1e4b9ad3b8b | 224 | } |
JonFreeman | 12:d1d21a2941ef | 225 | |
JonFreeman | 12:d1d21a2941ef | 226 | /** |
JonFreeman | 12:d1d21a2941ef | 227 | * void brushless_motor::speed_monitor_and_control () // ** CALL THIS 32 TIMES PER SECOND ** |
JonFreeman | 12:d1d21a2941ef | 228 | * Call this once per 'MAIN_LOOP_REPEAT_TIME_US= 31250' main loop pass to keep RPM and MPH figures correct |
JonFreeman | 12:d1d21a2941ef | 229 | * Tracks total transitions on Hall sensor inputs to determine speed. |
JonFreeman | 12:d1d21a2941ef | 230 | * Sets variables double dRPM of motor RPM, and double dMPH miles per hour |
JonFreeman | 12:d1d21a2941ef | 231 | * |
JonFreeman | 12:d1d21a2941ef | 232 | * Speed control - double target_speed as reference input. * |
JonFreeman | 12:d1d21a2941ef | 233 | * ** This is where any speed limit gets applied ** |
JonFreeman | 12:d1d21a2941ef | 234 | * Motor voltage reduced when at or over speed. Does NOT apply any braking |
JonFreeman | 12:d1d21a2941ef | 235 | * Scope for further improvement of control algorithm - crude implementation of PID with no I |
JonFreeman | 12:d1d21a2941ef | 236 | */ |
JonFreeman | 12:d1d21a2941ef | 237 | void brushless_motor::speed_monitor_and_control () // call this once per 'MAIN_LOOP_REPEAT_TIME_US= 31250' main loop pass to keep count = edges per sec |
JonFreeman | 16:d1e4b9ad3b8b | 238 | { // |
JonFreeman | 16:d1e4b9ad3b8b | 239 | // const double samp_scale = 0.275; // Tweak this value only to tune filter |
JonFreeman | 16:d1e4b9ad3b8b | 240 | const double samp_scale = 0.6; // Increased May 2020 to improve ssl speed settling time |
JonFreeman | 16:d1e4b9ad3b8b | 241 | const double shrink_by = 1.0 - samp_scale; |
JonFreeman | 16:d1e4b9ad3b8b | 242 | double rpm, speed_error; |
JonFreeman | 12:d1d21a2941ef | 243 | uint32_t Hall_tot_copy = Hall_total; // Copy value for use throughout function as may get changed at any instant during exec ! |
JonFreeman | 12:d1d21a2941ef | 244 | moving_flag = true; |
JonFreeman | 12:d1d21a2941ef | 245 | if (Hall_previous == Hall_tot_copy) { // Test for motor turning or not |
JonFreeman | 12:d1d21a2941ef | 246 | moving_flag = false; // Zero Hall transitions since previous call - motor not moving |
JonFreeman | 12:d1d21a2941ef | 247 | tickleon = TICKLE_TIMES; // May need to tickle some charge into high side switch bootstrap supplies |
JonFreeman | 16:d1e4b9ad3b8b | 248 | } |
JonFreeman | 16:d1e4b9ad3b8b | 249 | rpm = (double) (((Hall_tot_copy - Hall_previous) * 640) / motor_poles); // (Motor Hall sensor transitions in previous 31250us) * 640 |
JonFreeman | 16:d1e4b9ad3b8b | 250 | RPM_filter *= shrink_by; // rpm now true but lumpy 'RPM' during most recent 31250us corrected for number of motor poles |
JonFreeman | 16:d1e4b9ad3b8b | 251 | RPM_filter += (rpm * samp_scale); // filtered RPM |
JonFreeman | 16:d1e4b9ad3b8b | 252 | dRPM = RPM_filter; |
JonFreeman | 16:d1e4b9ad3b8b | 253 | dMPH = user_settings.rpm2mph(RPM_filter); // Completed updates of RPM and MPH |
JonFreeman | 16:d1e4b9ad3b8b | 254 | Hall_previous = Hall_tot_copy; |
JonFreeman | 16:d1e4b9ad3b8b | 255 | speed_error = (dMPH - user_settings.top_speed()); // 'P' Proportional contribution to PID control |
JonFreeman | 16:d1e4b9ad3b8b | 256 | bool clamp_change = false; |
JonFreeman | 16:d1e4b9ad3b8b | 257 | if (speed_error > 0.0 && (inner_mode == MOTOR_FORWARD || inner_mode == MOTOR_REVERSE)) { // Speed control only makes sense when motor runnable |
JonFreeman | 16:d1e4b9ad3b8b | 258 | V_clamp *= (1.0 - (speed_error / 60.0)); // Driving too fast, so lower clamp voltage a tiny bit |
JonFreeman | 16:d1e4b9ad3b8b | 259 | clamp_change = true; |
JonFreeman | 16:d1e4b9ad3b8b | 260 | } |
JonFreeman | 16:d1e4b9ad3b8b | 261 | else { // Not going too fast, and maybe driving or not |
JonFreeman | 16:d1e4b9ad3b8b | 262 | if (V_clamp < 0.99) { |
JonFreeman | 16:d1e4b9ad3b8b | 263 | V_clamp += 0.015; |
JonFreeman | 16:d1e4b9ad3b8b | 264 | if (V_clamp > 1.0) |
JonFreeman | 16:d1e4b9ad3b8b | 265 | V_clamp = 1.0; |
JonFreeman | 16:d1e4b9ad3b8b | 266 | clamp_change = true; |
JonFreeman | 16:d1e4b9ad3b8b | 267 | } |
JonFreeman | 16:d1e4b9ad3b8b | 268 | } |
JonFreeman | 16:d1e4b9ad3b8b | 269 | if (clamp_change) { |
JonFreeman | 16:d1e4b9ad3b8b | 270 | motor_voltage_refresh () ; |
JonFreeman | 16:d1e4b9ad3b8b | 271 | } |
JonFreeman | 16:d1e4b9ad3b8b | 272 | /* d = V_clamp + (speed_error * sdbl[2]) + ((dv_by_dt / 1000.0) * sdbl[4]); // Apply P+I+D (with I=0) control |
JonFreeman | 12:d1d21a2941ef | 273 | if (d > 1.0) d = 1.0; |
JonFreeman | 12:d1d21a2941ef | 274 | if (d < 0.0) d = 0.0; |
JonFreeman | 12:d1d21a2941ef | 275 | V_clamp = d; |
JonFreeman | 12:d1d21a2941ef | 276 | if (V_clamp < last_V) // Jan 2019 limit top speed when driving |
JonFreeman | 12:d1d21a2941ef | 277 | { |
JonFreeman | 12:d1d21a2941ef | 278 | d *= 0.95; // need limit, ffi see MCP1630 data |
JonFreeman | 12:d1d21a2941ef | 279 | d = 1.0 - d; // because pwm is wrong way up |
JonFreeman | 16:d1e4b9ad3b8b | 280 | //FUCKETYFUCK maxV.pulsewidth_ticks ((int)(d * MAX_PWM_TICKS)); // PWM output to MCP1630 inverted motor pwm as MCP1630 inverts |
JonFreeman | 12:d1d21a2941ef | 281 | maxV.pulsewidth_ticks ((int)(d * MAX_PWM_TICKS)); // PWM output to MCP1630 inverted motor pwm as MCP1630 inverts |
JonFreeman | 12:d1d21a2941ef | 282 | } |
JonFreeman | 16:d1e4b9ad3b8b | 283 | }*/ |
JonFreeman | 12:d1d21a2941ef | 284 | /* temp_tick++; |
JonFreeman | 16:d1e4b9ad3b8b | 285 | if (temp_tick > 50) { // one and a bit second |
JonFreeman | 12:d1d21a2941ef | 286 | temp_tick = 0; |
JonFreeman | 16:d1e4b9ad3b8b | 287 | pc.printf ("top speed %.1f, mph %.1f, speed_err %.1f, V_clamp%.3f\r\n", user_settings.top_speed(), dMPH, speed_error, V_clamp); |
JonFreeman | 12:d1d21a2941ef | 288 | } |
JonFreeman | 12:d1d21a2941ef | 289 | */ |
JonFreeman | 16:d1e4b9ad3b8b | 290 | } |
JonFreeman | 16:d1e4b9ad3b8b | 291 | |
JonFreeman | 16:d1e4b9ad3b8b | 292 | bool brushless_motor::exists () { |
JonFreeman | 16:d1e4b9ad3b8b | 293 | return ((Hall_index[0] > 0) && (Hall_index[0] < 7)) ; |
JonFreeman | 10:e40d8724268a | 294 | } |
JonFreeman | 10:e40d8724268a | 295 | |
JonFreeman | 10:e40d8724268a | 296 | bool brushless_motor::is_moving () |
JonFreeman | 10:e40d8724268a | 297 | { |
JonFreeman | 10:e40d8724268a | 298 | return moving_flag; |
JonFreeman | 10:e40d8724268a | 299 | } |
JonFreeman | 10:e40d8724268a | 300 | |
JonFreeman | 10:e40d8724268a | 301 | /** |
JonFreeman | 10:e40d8724268a | 302 | bool brushless_motor::set_mode (int m) |
JonFreeman | 16:d1e4b9ad3b8b | 303 | Use to set motor to one mode of HANDBRAKE, FORWARD, REVERSE. Used to also be used to set REGENBRAKE, replaced by brake(x) function. |
JonFreeman | 10:e40d8724268a | 304 | If this causes change of mode, also sets V and I to zero. |
JonFreeman | 10:e40d8724268a | 305 | */ |
JonFreeman | 16:d1e4b9ad3b8b | 306 | bool brushless_motor::set_mode (uint32_t m) |
JonFreeman | 10:e40d8724268a | 307 | { |
JonFreeman | 12:d1d21a2941ef | 308 | if ((m != MOTOR_HANDBRAKE) && (m != MOTOR_FORWARD) && (m != MOTOR_REVERSE) && (m != MOTOR_REGENBRAKE)) { |
JonFreeman | 10:e40d8724268a | 309 | // pc.printf ("Error in set_mode, invalid mode %d\r\n", m); |
JonFreeman | 10:e40d8724268a | 310 | return false; |
JonFreeman | 10:e40d8724268a | 311 | } |
JonFreeman | 10:e40d8724268a | 312 | if (visible_mode != m) { // Mode change, kill volts and amps to be safe |
JonFreeman | 10:e40d8724268a | 313 | set_V_limit (0.0); |
JonFreeman | 10:e40d8724268a | 314 | set_I_limit (0.0); |
JonFreeman | 10:e40d8724268a | 315 | visible_mode = m; |
JonFreeman | 10:e40d8724268a | 316 | } |
JonFreeman | 16:d1e4b9ad3b8b | 317 | if (m == MOTOR_FORWARD || m == MOTOR_REVERSE) // 8 or 16 - these are effectively address bits of motor pattern lut |
JonFreeman | 16:d1e4b9ad3b8b | 318 | m ^= direction; // direction set to 0 or (MOTOR_FORWARD | MOTOR_REVERSE), so has zero or two bits set |
JonFreeman | 10:e40d8724268a | 319 | inner_mode = m; // idea is to use inner_mode only in lut addressing, keep 'visible_mode' true regardless of setup data in eeprom |
JonFreeman | 10:e40d8724268a | 320 | return true; |
JonFreeman | 10:e40d8724268a | 321 | } |
JonFreeman | 10:e40d8724268a | 322 | |
JonFreeman | 16:d1e4b9ad3b8b | 323 | void brushless_motor::brake (double brake_effort) { |
JonFreeman | 16:d1e4b9ad3b8b | 324 | // pc.printf ("In motor::brake, user_settings.brake_effectiveness = %3f\r\n", user_settings.brake_effectiveness()); |
JonFreeman | 16:d1e4b9ad3b8b | 325 | set_mode (MOTOR_REGENBRAKE); |
JonFreeman | 16:d1e4b9ad3b8b | 326 | if (brake_effort > 1.0) |
JonFreeman | 16:d1e4b9ad3b8b | 327 | brake_effort = 1.0; |
JonFreeman | 16:d1e4b9ad3b8b | 328 | if (brake_effort < 0.0) |
JonFreeman | 16:d1e4b9ad3b8b | 329 | brake_effort = 0.0; |
JonFreeman | 16:d1e4b9ad3b8b | 330 | brake_effort *= user_settings.brake_effectiveness(); // set upper limit, this is essential - May2020 fixed, was 100 times too big |
JonFreeman | 16:d1e4b9ad3b8b | 331 | brake_effort = sqrt (brake_effort); // to linearise effect |
JonFreeman | 16:d1e4b9ad3b8b | 332 | set_V_limit (brake_effort) ; |
JonFreeman | 16:d1e4b9ad3b8b | 333 | set_I_limit (1.0); |
JonFreeman | 16:d1e4b9ad3b8b | 334 | V_clamp = 1.0; |
JonFreeman | 16:d1e4b9ad3b8b | 335 | } |
JonFreeman | 16:d1e4b9ad3b8b | 336 | |
JonFreeman | 16:d1e4b9ad3b8b | 337 | uint32_t brushless_motor::get_mode () { |
JonFreeman | 16:d1e4b9ad3b8b | 338 | return visible_mode; |
JonFreeman | 16:d1e4b9ad3b8b | 339 | } |
JonFreeman | 16:d1e4b9ad3b8b | 340 | |
JonFreeman | 12:d1d21a2941ef | 341 | void brushless_motor::motor_set () // Energise Port with data determined by Hall sensors |
JonFreeman | 10:e40d8724268a | 342 | { |
JonFreeman | 11:bfb73f083009 | 343 | Hall_index[0] = read_Halls (); |
JonFreeman | 11:bfb73f083009 | 344 | OP = lut[inner_mode | Hall_index[0]]; |
JonFreeman | 10:e40d8724268a | 345 | } |
JonFreeman | 12:d1d21a2941ef | 346 |