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
Revision 13:ef7a06fa11de, committed 2019-09-29
- Comitter:
- JonFreeman
- Date:
- Sun Sep 29 16:34:37 2019 +0000
- Parent:
- 12:d1d21a2941ef
- Child:
- 14:acaa1add097b
- Commit message:
- Stable code as at end of 2019 running season
Changed in this revision
--- a/24LC64_eeprom.cpp Mon Mar 04 17:51:08 2019 +0000 +++ b/24LC64_eeprom.cpp Sun Sep 29 16:34:37 2019 +0000 @@ -1,5 +1,5 @@ #include "mbed.h" -#include "DualBLS.h" +#include "STM3_ESC.h" #include "BufferedSerial.h" extern BufferedSerial pc; extern error_handling_Jan_2019 ESC_Error ; // Provides array usable to store error codes. @@ -195,7 +195,7 @@ pc.printf ("in wr_24LC64, device thought good, mem addr write worked, failed writing string\r\n"); return false; } - pc.printf ("In wr_24LC64 No Errors Found!\r\n"); +// pc.printf ("In wr_24LC64 No Errors Found!\r\n"); return true; }
--- a/DualBLS.h Mon Mar 04 17:51:08 2019 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,125 +0,0 @@ -#include "mbed.h" - -#ifndef MBED_DUALBLS_H -#define MBED_DUALBLS_H - -//#define USING_DC_MOTORS // Uncomment this to play with Dinosaur DC motors - -#include "BufferedSerial.h" -const int MOTOR_HANDBRAKE = 0, - MOTOR_FORWARD = 8, - MOTOR_REVERSE = 16, - MOTOR_REGENBRAKE = 24; - -const int TIMEOUT_SECONDS = 2; - -/* Please Do Not Alter these */ -const int PWM_PRESECALER_DEFAULT = 2, - VOLTAGE_READ_INTERVAL_US = 50, // Interrupts timed every 50 micro sec, runs around loop performing 1 A-D conversion per pass - MAIN_LOOP_REPEAT_TIME_US = 31250, // 31250 us, with TACHO_TAB_SIZE = 32 means tacho_ticks_per_time is tacho_ticks_per_second - MAIN_LOOP_ITERATION_Hz = 1000000 / MAIN_LOOP_REPEAT_TIME_US, -// CURRENT_SAMPLES_AVERAGED = 100, // Current is spikey. Reading smoothed by using average of this many latest current readings - PWM_HZ = 15000, // chosen to be above cutoff frequency of average human ear -// PWM_HZ = 8000, // chosen to be above cutoff frequency of average human ear - clearly audible annoying noise - MAX_PWM_TICKS = (SystemCoreClock / (PWM_HZ * PWM_PRESECALER_DEFAULT)), - TICKLE_TIMES = 100 , - WATCHDOG_RELOAD = (TIMEOUT_SECONDS * 8); // WatchDog counter ticked down in 8Hz loop - -/* End of Please Do Not Alter these */ -const double PI = 4.0 * atan(1.0), - TWOPI = 8.0 * atan(1.0); - -enum {COM_SOURCES, COM1, COM2, HAND, RC_IN1, RC_IN2,THEEND} ; - -enum {MOTADIR, MOTBDIR, MOTAPOLES, MOTBPOLES, ISHUNTA, ISHUNTB, SVO1, SVO2, RCIN1, RCIN2, - COMM_SRC, BOARD_ID, TOP_SPEED, WHEELDIA, MOTPIN, WHEELGEAR, - FUT1, FUT2, FUT3, FUT4, FUT5} ; // - -enum { - FAULT_0, - FAULT_EEPROM, - FAULT_BOARD_ID, - FAULT_COM_LINE_LEN, - FAULT_COM_LINE_NOMATCH, - FAULT_COM_LINE_LEN_PC, - FAULT_COM_LINE_LEN_TS, - FAULT_COM_LINE_NOMATCH_PC, - FAULT_COM_LINE_NOMATCH_TS, - FAULT_UNRECOGNISED_STATE, - FAULT_MAX, - NUMOF_REPORTABLE_TS_ERRORS - } ; - -class error_handling_Jan_2019 -{ - int32_t ESC_fault[NUMOF_REPORTABLE_TS_ERRORS] ; // Some number of reportable error codes, accessible through set and read members - public: - error_handling_Jan_2019 () { // default constructor - for (int i = 0; i < (sizeof(ESC_fault) / sizeof(int32_t)); i++) - ESC_fault[i] = 0; - } - void set (uint32_t, int32_t) ; - void clr (uint32_t) ; - uint32_t read (uint32_t) ; - bool all_good () ; - void report_any (bool) ; // retain ? true or false -} ; - -enum {SOURCE_PC, SOURCE_TS} ; -const int BROADCAST = '\r'; -const int MAX_PARAMS = 20; -const int MAX_CMD_LEN = 220; - -struct parameters { - struct kb_command const * command_list; - BufferedSerial * com; // pc or com2 - int32_t position_in_list, numof_dbls, target_unit, source, numof_menu_items; - double dbl[MAX_PARAMS]; - bool respond, resp_always; -} ; - -class cli_2019 { - struct kb_command const * commandlist ; - int clindex; - char cmdline[MAX_CMD_LEN + 8]; - char * cmdline_ptr; - parameters a ; -public: - cli_2019 (BufferedSerial * comport, kb_command const * list, int list_len, int source) { - a.com = comport ; - a.command_list = commandlist = list ; - a.numof_menu_items = list_len ; - a.source = source ; - cmdline_ptr = cmdline; - clindex = 0; - if (source == SOURCE_PC) - a.resp_always = true; - else - a.resp_always = false; - } ; - void core () ; - void test () ; -} ; - -class eeprom_settings { - I2C i2c; - uint32_t errors; - char settings [36]; - bool rd_24LC64 (int start_addr, char * dest, int length) ; - bool wr_24LC64 (int start_addr, char * dest, int length) ; - bool set_24LC64_internal_address (int start_addr) ; - bool ack_poll () ; - public: - eeprom_settings (PinName sda, PinName scl); // Constructor - char rd (uint32_t) ; // Read one setup char value from private buffer 'settings' - bool wr (char, uint32_t) ; // Write one setup char value to private buffer 'settings' - bool save () ; // Write 'settings' buffer to EEPROM - bool set_defaults (); // Put default settings into EEPROM and local buffer - uint32_t errs () ; // Return errors -} ; - - - - -#endif -
--- a/F401RE.h Mon Mar 04 17:51:08 2019 +0000 +++ b/F401RE.h Sun Sep 29 16:34:37 2019 +0000 @@ -1,3 +1,4 @@ +#include "STM3_ESC.h" // Feb 2018 Now using DGD21032 mosfet drivers via 74HC00 pwm gates (low side) - GOOD, works well with auto-tickle of high side drivers // Jan 2019 Trying to add two Radio Control inputs on PC_14 and PC_15, previously connected to unused LF Xtal. @@ -71,8 +72,9 @@ // Pin 1 VBAT NET +3V3 //DigitalIn J3 (PC_13, PullUp);// Pin 2 Jumper pulls to GND, R floats Hi +#ifdef TEMP_SENSOR_ENABLE InterruptIn Temperature_pin (PC_13);// Pin 2 June 2018 - taken for temperature sensor - hard wired to T1 due to wrong thought T1 could be InterruptIn - +#endif // Pin 3 PC14-OSC32_IN NET O32I Xtal chucked off these pins, now needed for RC inputs // Pin 4 PC15-OSC32_OUT NET O32O
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/F411RE.h Sun Sep 29 16:34:37 2019 +0000 @@ -0,0 +1,187 @@ +#include "STM3_ESC.h" +// Feb 2018 Now using DGD21032 mosfet drivers via 74HC00 pwm gates (low side) - GOOD, works well with auto-tickle of high side drivers + +// Jan 2019 Trying to add two Radio Control inputs on PC_14 and PC_15, previously connected to unused LF Xtal. +// Problem - Appears to conflict with serial port used for comms with controller +// Earlier efforts to use 'Servo' ports as 'you choose' between I/O failed as pins not capable of use as 'InterruptIn' + +// CORRECTION Comms problem with Touch Screen was insufficient pull-up on STM3_ESC opto. Change R12 from 1k to 470R + +// Experiment disabling RC inputs to see if clearing serial conflict is possible + + +// Port A -> MotorA, Port B -> MotorB +const uint16_t +// This is where port bits get assigned to motor output phase switches. +// Phases are U, V and W. +// Each phase uses two bits, one for the low side switch, one for the high side switch. +//MotorN_port_bits[] = {UL, VL, WL, UH, VH, WH}, // Order must be as shown - 3 low side switches U,V,W followed by 3 high side switches U,V,W +MotorA_port_bits[] = {0, 6, 4, 1, 7, 8}, // List of port A bits used to drive motor A UL, VL, WL, UH, VH, WH +MotorB_port_bits[] = {0, 1, 2, 10, 12, 13}, // List of port B bits used to drive motor B UL, VL, WL, UH, VH, WH +// Using port bit info in the two lines above, the compiler sorts all this into creation of lookup table +// to provide correct energisation sequencing as motors rotate. +// You need concern yourself no further about any of this. + + +AUL = (1 << MotorA_port_bits[0]), +AVL = (1 << MotorA_port_bits[1]), // These are which port bits connect to which mosfet driver +AWL = (1 << MotorA_port_bits[2]), + +AUH = (1 << MotorA_port_bits[3]), +AVH = (1 << MotorA_port_bits[4]), +AWH = (1 << MotorA_port_bits[5]), + +AUHVL = AUH | AVL, // Each of 6 possible output energisations made up of one hi and one low +AVHUL = AVH | AUL, +AUHWL = AUH | AWL, +AWHUL = AWH | AUL, +AVHWL = AVH | AWL, +AWHVL = AWH | AVL, + +KEEP_L_MASK_A = AUL | AVL | AWL, +KEEP_H_MASK_A = AUH | AVH | AWH, + +BRA = AUL | AVL | AWL, // All low side switches on (and all high side off) for braking + +BUL = (1 << MotorB_port_bits[0]), // Likewise for MotorB but different port bits on different port +BVL = (1 << MotorB_port_bits[1]), +BWL = (1 << MotorB_port_bits[2]), + +BUH = (1 << MotorB_port_bits[3]), +BVH = (1 << MotorB_port_bits[4]), +BWH = (1 << MotorB_port_bits[5]), + +BUHVL = BUH | BVL, +BVHUL = BVH | BUL, +BUHWL = BUH | BWL, +BWHUL = BWH | BUL, +BVHWL = BVH | BWL, +BWHVL = BWH | BVL, + +KEEP_L_MASK_B = BUL | BVL | BWL, +KEEP_H_MASK_B = BUH | BVH | BWH, + +BRB = BUL | BVL | BWL, + +PORT_A_MASK = AUL | AVL | AWL | AUH | AVH | AWH, // NEW METHOD FOR DGD21032 MOSFET DRIVERS +PORT_B_MASK = BUL | BVL | BWL | BUH | BVH | BWH; + +//PortOut MotA (PortA, PORT_A_MASK); // Activate output ports to motor drivers +//PortOut MotB (PortB, PORT_B_MASK); + +// Pin 1 VBAT NET +3V3 + +//DigitalIn J3 (PC_13, PullUp);// Pin 2 Jumper pulls to GND, R floats Hi +#ifdef TEMP_SENSOR_ENABLE +InterruptIn Temperature_pin (PC_13);// Pin 2 June 2018 - taken for temperature sensor - hard wired to T1 due to wrong thought T1 could be InterruptIn +#endif + +// Pin 3 PC14-OSC32_IN NET O32I Xtal chucked off these pins, now needed for RC inputs +// Pin 4 PC15-OSC32_OUT NET O32O +// Pin 5 PH0-OSC_IN NET PH1 +// Pin 6 PH1-OSC_OUT NET PH1 +// Pin 7 NRST NET NRST +AnalogIn Ain_DriverPot (PC_0); // Pin 8 Spare Analogue in, net SAIN fitted with external pull-down +AnalogIn Ain_SystemVolts (PC_1); // Pin 9 +#define MOT_A_I_ADC PC_2 +#define MOT_B_I_ADC PC_3 +//AnalogIn Motor_A_Current (PC_2); // Pin 10 +//AnalogIn Motor_B_Current (PC_3); // Pin 11 +// Pin 12 VSSA/VREF- NET GND +// Pin 13 VDDA/VREF+ NET +3V3 +// Pin 14 Port_A AUL +// Pin 15 Port_A AUH +// Pins 16, 17 BufferedSerial pc +BufferedSerial pc (PA_2, PA_3, 2048, 4, NULL); // Pins 16, 17 tx, rx to pc via usb lead +// Pin 18 VSS NET GND +// Pin 19 VDD NET +3V3 +// Pin 20 Port_A AWL +// Pin 21 DigitalOut led1(LED1); +DigitalOut LED (PA_5); // Pin 21 +// Pin 22 Port_A AVL +// Pin 23 Port_A AVH +//InterruptIn MBH2 (PC_4); // Pin 24 +//InterruptIn MBH3 (PC_5); // Pin 25 +#define _MBH2 PC_4 +#define _MBH3 PC_5 +// Pin 26 Port_B BUL +// Pin 27 Port_B BVL +// Pin 28 Port_B BWL +// Pin 29 Port_B BUH +// Pin 30 VCAP1 +// Pin 31 VSS +// Pin 32 VDD +// Pin 33 Port_B BVH +// Pin 34 Port_B BWH +DigitalOut T4 (PB_14); // Pin 35 +DigitalOut T3 (PB_15); // Pin 36 +// BufferedSerial com2 pins 37 Tx, 38 Rx +BufferedSerial com2 (PC_6, PC_7); // Pins 37, 38 tx, rx to Touch Screen Controller +#define APWMV PC_8 +#define APWMI PC_9 +//FastPWM A_MAX_V_PWM (PC_8, PWM_PRESECALER_DEFAULT), // Pin 39 pwm3/3 +// A_MAX_I_PWM (PC_9, PWM_PRESECALER_DEFAULT); // pin 40, prescaler value pwm3/4 +//InterruptIn MotB_Hall (PA_8); // Pin 41 +// Pin 41 Port_A AWH +// BufferedSerial com3 pins 42 Tx, 43 Rx +//InterruptIn tryseredge (PA_9); +BufferedSerial com3 (PA_9, PA_10); // Pins 42, 43 tx, rx to any aux module +// PA_9 is Tx. I wonder, can we also use InterruptIn on this pin to generate interrupts on tx bit transitions ? Let's find out ! +// No. + +// Feb 2018 Pins 44 and 45 now liberated, could use for serial or other uses +//BufferedSerial extra_ser (PA_11, PA_12); // Pins 44, 45 tx, rx to XBee module +DigitalOut T2 (PA_11); // Pin 44 +// was DigitalOut T1 (PA_12); // Pin 45 + + +//InterruptIn T1 (PA_12); // Pin 45 now input counting pulses from LMT01 temperature sensor +// InterruptIn DOES NOT WORK ON PA_12. Boards are being made, will have to wire link PA12 to PC13 +DigitalIn T1 (PA_12); +////InterruptIn T1 (PC_13); // Pin 45 now input counting pulses from LMT01 temperature sensor + + + +// Pin 46 SWDIO +// Pin 47 VSS +// Pin 48 VDD +// Pin 49 SWCLK + +//Was DigitalOut T5 (PA_15); // Pin 50 +DigitalIn T5 (PA_15); // Pin 50 now fwd/rev from remote control box if fitted +#define _MAH1 PC_10 // Pin 51 +#define _MAH2 PC_11 // Pin 52 +#define _MAH3 PC_12 // Pin 53 +//InterruptIn MBH1 (PD_2); // Pin 54 +#define _MBH1 PD_2 +DigitalOut T6 (PB_3); // Pin 55 +#define BPWMV PB_4 +#define BPWMI PB_5 +//FastPWM B_MAX_V_PWM (PB_4, PWM_PRESECALER_DEFAULT), // Pin 56 pwm3/3 +// B_MAX_I_PWM (PB_5, PWM_PRESECALER_DEFAULT); // pin 57, prescaler value pwm3/4 + +//I2C i2c (PB_7, PB_6); // Pins 58, 59 For 24LC64 eeprom +#define SDA_PIN PB_7 +#define SCL_PIN PB_6 +// Pin 60 BOOT0 + +// Servo pins, 2 off. Configured as Input to read radio control receiver +// ** Update December 2018 ** +// These pins can not be used as InterruptIn. +// Can be used as outputs by 'Servo' +// If used as servo output, code gives pin to 'Servo' - seems to work +//InterruptIn Servo1_i (PB_8); // Pin 61 to read output from rc rx +//InterruptIn Servo2_i (PB_9); // Pin 62 to read output from rc rx +// *** NOTE *** Above InterruptIn Servo using PB pins seems not to work, probably due to other Port B pins used as PortOut (try PortInOut?) +// Nov 2018 - Yet to try using PC14, PC15, free now as 32k768 xtal not fitted + + +// Pin 63 VSS +// Pin 64 VDD +// SYSTEM CONSTANTS +// December 2018 ** NEED TO PROVE SERVO OUT WORKS ** YES, DONE. + Servo Servo1 (PB_8) ; +// Servos[0] = & Servo1; + Servo Servo2 (PB_9) ; +// Servos[1] = & Servo2; +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/STM3_ESC.h Sun Sep 29 16:34:37 2019 +0000 @@ -0,0 +1,127 @@ +#include "mbed.h" + +#ifndef MBED_DUALBLS_H +#define MBED_DUALBLS_H + +//#define USING_DC_MOTORS // Uncomment this to play with Dinosaur DC motors + +//#define TEMP_SENSOR_ENABLE // + +#include "BufferedSerial.h" +const int MOTOR_HANDBRAKE = 0, + MOTOR_FORWARD = 8, + MOTOR_REVERSE = 16, + MOTOR_REGENBRAKE = 24; + +const int TIMEOUT_SECONDS = 2; + +/* Please Do Not Alter these */ +const int PWM_PRESECALER_DEFAULT = 2, + VOLTAGE_READ_INTERVAL_US = 50, // Interrupts timed every 50 micro sec, runs around loop performing 1 A-D conversion per pass + MAIN_LOOP_REPEAT_TIME_US = 31250, // 31250 us, with TACHO_TAB_SIZE = 32 means tacho_ticks_per_time is tacho_ticks_per_second + MAIN_LOOP_ITERATION_Hz = 1000000 / MAIN_LOOP_REPEAT_TIME_US, +// CURRENT_SAMPLES_AVERAGED = 100, // Current is spikey. Reading smoothed by using average of this many latest current readings + PWM_HZ = 15000, // chosen to be above cutoff frequency of average human ear +// PWM_HZ = 8000, // chosen to be above cutoff frequency of average human ear - clearly audible annoying noise + MAX_PWM_TICKS = (SystemCoreClock / (PWM_HZ * PWM_PRESECALER_DEFAULT)), + TICKLE_TIMES = 100 , + WATCHDOG_RELOAD = (TIMEOUT_SECONDS * 8); // WatchDog counter ticked down in 8Hz loop + +/* End of Please Do Not Alter these */ +const double PI = 4.0 * atan(1.0), + TWOPI = 8.0 * atan(1.0); + +enum {COM_SOURCES, COM1, COM2, HAND, RC_IN1, RC_IN2,THEEND} ; + +enum {MOTADIR, MOTBDIR, MOTAPOLES, MOTBPOLES, ISHUNTA, ISHUNTB, SVO1, SVO2, RCIN1, RCIN2, + COMM_SRC, BOARD_ID, TOP_SPEED, WHEELDIA, MOTPIN, WHEELGEAR, + FUT1, FUT2, FUT3, FUT4, FUT5} ; // + +enum { + FAULT_0, + FAULT_EEPROM, + FAULT_BOARD_ID, + FAULT_COM_LINE_LEN, + FAULT_COM_LINE_NOMATCH, + FAULT_COM_LINE_LEN_PC, + FAULT_COM_LINE_LEN_TS, + FAULT_COM_LINE_NOMATCH_PC, + FAULT_COM_LINE_NOMATCH_TS, + FAULT_UNRECOGNISED_STATE, + FAULT_MAX, + NUMOF_REPORTABLE_TS_ERRORS + } ; + +class error_handling_Jan_2019 +{ + int32_t ESC_fault[NUMOF_REPORTABLE_TS_ERRORS] ; // Some number of reportable error codes, accessible through set and read members + public: + error_handling_Jan_2019 () { // default constructor + for (int i = 0; i < (sizeof(ESC_fault) / sizeof(int32_t)); i++) + ESC_fault[i] = 0; + } + void set (uint32_t, int32_t) ; + void clr (uint32_t) ; + uint32_t read (uint32_t) ; + bool all_good () ; + void report_any (bool) ; // retain ? true or false +} ; + +enum {SOURCE_PC, SOURCE_TS} ; +const int BROADCAST = '\r'; +const int MAX_PARAMS = 20; +const int MAX_CMD_LEN = 220; + +struct parameters { + struct kb_command const * command_list; + BufferedSerial * com; // pc or com2 + int32_t position_in_list, numof_dbls, target_unit, source, numof_menu_items; + double dbl[MAX_PARAMS]; + bool respond, resp_always; +} ; + +class cli_2019 { + struct kb_command const * commandlist ; + int clindex; + char cmdline[MAX_CMD_LEN + 8]; + char * cmdline_ptr; + parameters a ; +public: + cli_2019 (BufferedSerial * comport, kb_command const * list, int list_len, int source) { + a.com = comport ; + a.command_list = commandlist = list ; + a.numof_menu_items = list_len ; + a.source = source ; + cmdline_ptr = cmdline; + clindex = 0; + if (source == SOURCE_PC) + a.resp_always = true; + else + a.resp_always = false; + } ; + void core () ; + void test () ; +} ; + +class eeprom_settings { + I2C i2c; + uint32_t errors; + char settings [36]; + bool rd_24LC64 (int start_addr, char * dest, int length) ; + bool wr_24LC64 (int start_addr, char * dest, int length) ; + bool set_24LC64_internal_address (int start_addr) ; + bool ack_poll () ; + public: + eeprom_settings (PinName sda, PinName scl); // Constructor + char rd (uint32_t) ; // Read one setup char value from private buffer 'settings' + bool wr (char, uint32_t) ; // Write one setup char value to private buffer 'settings' + bool save () ; // Write 'settings' buffer to EEPROM + bool set_defaults (); // Put default settings into EEPROM and local buffer + uint32_t errs () ; // Return errors +} ; + + + + +#endif +
--- a/brushless_motor.cpp Mon Mar 04 17:51:08 2019 +0000 +++ b/brushless_motor.cpp Sun Sep 29 16:34:37 2019 +0000 @@ -1,8 +1,9 @@ // 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 "stm32f401xe.h" +//#include "stm32f411xe.h" +#include "STM3_ESC.h" #include "BufferedSerial.h" #include "FastPWM.h" #include "Servo.h" @@ -47,10 +48,10 @@ numof_current_sense_rs = 1.0; RPM_filter = 0.0; dv_by_dt = 0.0; - s[1] = 0.25; - s[2] = 9.0; - s[3] = 0.4; - s[4] = 0.2; + sdbl[1] = 0.25; // Remind me. What are these all about ? + sdbl[2] = 9.0; + sdbl[3] = 0.4; + sdbl[4] = 0.2; dRPM = 0.0; V_clamp = 1.0 ; // Used to limit top speed motor_poles = 8; // Default to 8 pole motor @@ -80,7 +81,7 @@ max_rpm = (uint32_t) (((double)mode.rd(TOP_SPEED) / rpm2mph) / 10.0) ; target_speed = (double)mode.rd(TOP_SPEED) / 10.0; numof_current_sense_rs = (double)mode.rd(current_sense_rs_offset); - pc.printf ("max_rpm=%d, tp speed=%.1f, rpm2mph=%.6f\r\n", max_rpm, target_speed, rpm2mph); + pc.printf ("max_rpm=%d, top speed=%.1f, rpm2mph=%.6f\r\n", max_rpm, target_speed, rpm2mph); } if (p == 4 || p == 6 || p == 8) { motor_poles = p; @@ -152,6 +153,11 @@ } +extern double Current_Scaler_Sep_2019; +extern int WatchDog; +#define DRIVING (visible_mode == MOTOR_FORWARD || visible_mode == MOTOR_REVERSE) +#define ESTOP (WatchDog == 0 && DRIVING) + /** * void brushless_motor::set_V_limit (double p) // Sets max motor voltage. * @@ -159,13 +165,13 @@ */ void brushless_motor::set_V_limit (double p) // Sets max motor voltage. { - if (p < 0.0) + if (p < 0.0 || ESTOP) p = 0.0; if (p > 1.0) p = 1.0; last_V = p; // Retains last voltage limit demanded by driver - if ((V_clamp < last_V) && (inner_mode == MOTOR_FORWARD || inner_mode == MOTOR_REVERSE)) // Jan 2019 limit top speed when driving + if ((V_clamp < last_V) && DRIVING) // Jan 2019 limit top speed when driving p = V_clamp; // If motor runnable, set voltage limit to lower of last_V and V_clamp p *= 0.95; // need limit, ffi see MCP1630 data @@ -173,6 +179,7 @@ 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 * * Set motor current limit from zero (p=0.0) to max determined by current sense resistors (p=1.0) @@ -195,14 +202,22 @@ * * Board designed to have 1, 2, 3 or 4 0R05 current sense resistors per motor for 10A, 20A, 30A or 40A peak motor currents */ + void brushless_motor::set_I_limit (double p) // Sets max motor current. pwm integrated to dc ref voltage level { const uint32_t MPR = ((MAX_PWM_TICKS * 9) / 11); // Scales 3.3v pwm DAC output to 2.7v V_Ref input - if (p < 0.0) + if (p < 0.0 || ESTOP) p = 0.0; if (p > 1.0) p = 1.0; last_I = p; // Retains last current limit demanded by driver + if (DRIVING) { + if (Current_Scaler_Sep_2019 > 1.0) + Current_Scaler_Sep_2019 = 1.0; + if (Current_Scaler_Sep_2019 < 0.0) + Current_Scaler_Sep_2019 = 0.0; + p *= Current_Scaler_Sep_2019; + } maxI.pulsewidth_ticks ((uint32_t)(p * MPR)); // PWM } @@ -226,10 +241,10 @@ #endif // Feb 2019 - coefficients currently values in ram to allow for tweaking via command line. Will be 'const' once settled upon. // const double samp_scale = 0.35; // Tweak this value only to tune filter - double samp_scale = s[1]; // Tweak this value only to tune filter + double samp_scale = sdbl[1]; // Tweak this value only to tune filter double shrink_by = 1.0 - samp_scale; // const double dv_scale = 0.15; - double dv_scale = s[3]; + double dv_scale = sdbl[3]; double dv_shrink = 1.0 - dv_scale; double speed_error, d, t; uint32_t Hall_tot_copy = Hall_total; // Copy value for use throughout function as may get changed at any instant during exec ! @@ -253,7 +268,7 @@ if (inner_mode == MOTOR_FORWARD || inner_mode == MOTOR_REVERSE) { // Speed control only makes sense when motor runnable speed_error = (target_speed - dMPH) / 1000.0; // 'P' Proportional contribution to PID control - d = V_clamp + (speed_error * s[2]) + ((dv_by_dt / 1000.0) * s[4]); // Apply P+I+D (with I=0) control + d = V_clamp + (speed_error * sdbl[2]) + ((dv_by_dt / 1000.0) * sdbl[4]); // Apply P+I+D (with I=0) control if (d > 1.0) d = 1.0; if (d < 0.0) d = 0.0; V_clamp = d;
--- a/brushless_motor.h Mon Mar 04 17:51:08 2019 +0000 +++ b/brushless_motor.h Sun Sep 29 16:34:37 2019 +0000 @@ -6,7 +6,7 @@ #define MBED_BRUSHLESSMOTOR_H #include "mbed.h" -#include "DualBLS.h" +#include "STM3_ESC.h" #include "FastPWM.h" class brushless_motor @@ -15,7 +15,7 @@ uint32_t Hall_index[2], encoder_error_cnt, motor_poles, current_sense_rs_offset; uint32_t Hall_total, // Incremented on every Hall sensor transition Hall_previous, - visible_mode, + visible_mode, // One of MOTOR_HANDBRAKE, MOTOR_FORWARD, MOTOR_REVERSE, MOTOR_REGENBRAKE inner_mode; uint32_t direction; int temp_tick; @@ -39,7 +39,10 @@ uint32_t tickleon; double Idbl; double last_V, last_I; - double dRPM, dMPH, s[8]; + double dRPM, dMPH, + sdbl[8]; // Filter coefficients for filtering RPM and MPH reading stuff, I think! + // Used in brushless_motor::speed_monitor_and_control () + // brushless_motor () {} ; // can not use this with exotic elements PortOut, FastPWM etc brushless_motor (PinName iadc, PinName pwv, PinName pwi, const uint16_t *, PinName h1, PinName h2, PinName h3, PortName, int, uint32_t) ; // Constructor bool poles (int) ; // Set number of motor poles - 4, 6, or 8
--- a/cli_BLS_nortos.cpp Mon Mar 04 17:51:08 2019 +0000 +++ b/cli_BLS_nortos.cpp Sun Sep 29 16:34:37 2019 +0000 @@ -18,7 +18,7 @@ // Brushless_STM3_Ctrl_2018_11 #include "mbed.h" #include "BufferedSerial.h" -#include "DualBLS.h" +#include "STM3_ESC.h" #include "brushless_motor.h" #include <cctype> @@ -26,8 +26,8 @@ extern eeprom_settings mode ; extern error_handling_Jan_2019 ESC_Error ; // Provides array usable to store error codes. -extern int WatchDog; -extern bool WatchDogEnable; +extern int WatchDog; // from main +extern bool WatchDogEnable; // from main extern double rpm2mph ; extern brushless_motor MotorA, MotorB; // Controlling two motors together or individually @@ -202,9 +202,9 @@ break; case 2: // MotorA_ current sense resistors [1 to 4], MotorB_ current sense resistors [1 to 4] if (temps[1] > 0 && temps[1] < 5) - mode.wr(temps[1], MOTADIR); + mode.wr(temps[1], ISHUNTA); // Corrected since published if (temps[2] > 0 && temps[2] < 5) - mode.wr(temps[2], MOTBDIR); + mode.wr(temps[2], ISHUNTB); break; case 3: // 2 Servo1 [0 or 1], Servo2 [0 or 1] if (temps[1] == 0 || temps[1] == 1) @@ -265,6 +265,16 @@ } + +void ssl_cmd (struct parameters & a) { // set speed limit NEW and untested July 2019 + if (a.dbl[0] > 25.0) a.dbl[0] = 25.0; + if (a.dbl[0] < 1.0) a.dbl[0] = 1.0; + mode.wr((char)(a.dbl[0] * 10.0), TOP_SPEED); + mode.save (); +} + +#ifdef TEMP_SENSOR_ENABLE + extern uint32_t last_temperature_count; /** * void temperature_cmd (struct parameters & a) { @@ -275,6 +285,7 @@ a.com->printf ("tem%c %d\r\n", mode.rd(BOARD_ID), (last_temperature_count / 16) - 50); } } +#endif /** *void rpm_cmd (struct parameters & a) // to report e.g. RPM 1000 1000 ; speed for both motors @@ -352,6 +363,10 @@ /** *void kd_cmd (struct parameters & a) // kick and enable the watch dog * +* Brute_TS_Controller or other external controller to issue regular 'kd\r' to come here. +* WatchDog disabled by default, enabled on first call to here +* This is where WatchDog timer is reset and reloaded. +* Timeout may be detected and handled in 8Hz loop in main programme loop */ void kd_cmd (struct parameters & a) // kick the watchdog. Reached from TS or pc. { @@ -359,6 +374,10 @@ WatchDogEnable = true; // Receipt of this command sufficient to enable watchdog } +void wd_report (struct parameters & a) // Reachable always from pc. Only addressed board responds to TS +{ + pc.printf ("WatchDog %d\r\n", WatchDog); +} /** *void who_cmd (struct parameters & a) // Reachable always from pc. Only addressed board responds to TS * @@ -386,16 +405,16 @@ { switch ((int)a.dbl[0]) { case 1: - MotorA.s[1] = MotorB.s[1] = a.dbl[1]; + MotorA.sdbl[1] = MotorB.sdbl[1] = a.dbl[1]; break; case 2: - MotorA.s[2] = MotorB.s[2] = a.dbl[1]; + MotorA.sdbl[2] = MotorB.sdbl[2] = a.dbl[1]; break; case 3: - MotorA.s[3] = MotorB.s[3] = a.dbl[1]; + MotorA.sdbl[3] = MotorB.sdbl[3] = a.dbl[1]; break; case 4: - MotorA.s[4] = MotorB.s[4] = a.dbl[1]; + MotorA.sdbl[4] = MotorB.sdbl[4] = a.dbl[1]; break; case 5: MotorA.set_speed (a.dbl[1]); @@ -404,11 +423,12 @@ default: pc.printf ("Wrong use of scmd %f\r\n", a.dbl[0]); } + pc.printf ("Filter Coefficient Fiddler - used in brushless_motor::speed_monitor_and_control ()"); pc.printf ("Filter Coeffs 1 to 4\r\n"); - pc.printf ("1 %.3f\tPscale 0.01-0.5\r\n", MotorA.s[1]); - pc.printf ("2 %.3f\tP_gain 1.0-1000.0\r\n", MotorA.s[2]); - pc.printf ("3 %.3f\tDscale 0.01-0.5\r\n", MotorA.s[3]); - pc.printf ("4 %.3f\tD_gain 1.0-1000.0\r\n", MotorA.s[4]); + pc.printf ("1 %.3f\tPscale 0.01-0.5\r\n", MotorA.sdbl[1]); + pc.printf ("2 %.3f\tP_gain 1.0-1000.0\r\n", MotorA.sdbl[2]); + pc.printf ("3 %.3f\tDscale 0.01-0.5\r\n", MotorA.sdbl[3]); + pc.printf ("4 %.3f\tD_gain 1.0-1000.0\r\n", MotorA.sdbl[4]); pc.printf ("5 Set target_speed\r\n"); } @@ -456,8 +476,11 @@ {"?v", "Report system bus voltage", sysV_report}, {"?i", "Report motor both currents", sysI_report}, {"who", "search for connected units, e.g. 3who returs 'who3' if found", who_cmd}, +#ifdef TEMP_SENSOR_ENABLE {"tem", "report temperature", temperature_cmd}, +#endif {"mph", "read loco speed miles per hour", mph_cmd}, + {"ssl", "set speed limit e.g. 10.7", ssl_cmd}, // NEW July 2019 // {"rvi", "read most recent values sent to pwms", rvi_cmd}, // {"rdi", "read motor currents and power voltage", rdi_cmd}, // ***** Endof @@ -474,7 +497,7 @@ #ifdef USING_DC_MOTORS {"mtypes", "report types of motors found", mt_cmd}, #endif - {"s","1-4, filter param", scmd}, + {"s","1-4, RPM and speed filter param", scmd}, {"pot", "read drivers control pot", pot_cmd}, {"fw", "forward", fw_cmd}, {"re", "reverse", re_cmd}, @@ -485,10 +508,14 @@ {"vi", "set motors V and I percent RANGE 0 to 100", vi_cmd}, {"?v", "Report system bus voltage", sysV_report}, {"?i", "Report motor both currents", sysI_report}, + {"?w", "show WatchDog timer contents", wd_report}, {"who", "search for connected units, e.g. 3who returs 'who3' if found", who_cmd}, {"mode", "read or set params in eeprom", mode19_cmd}, // Big change Jan 2019 + {"ssl", "set speed limit e.g. 10.7", ssl_cmd}, // NEW July 2019 ONLY HERE FOR TEST, normal use is from Touch Screen only. // {"erase", "set eeprom contents to all 0xff", erase_cmd}, +#ifdef TEMP_SENSOR_ENABLE {"tem", "report temperature", temperature_cmd}, // Reports -50 when sensor not fitted +#endif {"kd", "kick the dog, reloads WatchDog", kd_cmd}, {"ver", "Version", ver_cmd}, {"rcin", "Report Radio Control Input stuff", rcin_pccmd},
--- a/error_handler.cpp Mon Mar 04 17:51:08 2019 +0000 +++ b/error_handler.cpp Sun Sep 29 16:34:37 2019 +0000 @@ -1,5 +1,5 @@ #include "mbed.h" -#include "DualBLS.h" +#include "STM3_ESC.h" #include "BufferedSerial.h" extern BufferedSerial pc;
--- a/main.cpp Mon Mar 04 17:51:08 2019 +0000 +++ b/main.cpp Sun Sep 29 16:34:37 2019 +0000 @@ -1,29 +1,34 @@ -// Cloned from 'DualBLS2018_06' on 23 November 2018 +/* + STM3_ESC Electronic Speed Controller board, drives Two Brushless Motors, full Four Quadrant Control. + Jon Freeman B. Eng Hons + 2015 - 2019 +*/ #include "mbed.h" -//#include "users/mbed_official/code/mbed-dev/file/707f6e361f3e/targets/TARGET_STM/TARGET_STM32F4/TARGET_STM32F401xE/device/stm32f401xe.h" -#include "DualBLS.h" - -#include "stm32f401xe.h" +#include "STM3_ESC.h" #include "BufferedSerial.h" #include "FastPWM.h" #include "Servo.h" #include "brushless_motor.h" #include "Radio_Control_In.h" +//#ifdef TARGET_NUCLEO_F401RE // +//#endif /* -Brushless_STM3 board +Brushless_STM3_ESC board Jan 2019 * WatchDog implemented. Default is disabled, 'kd' command from TS controller enables and reloads * Tidied brushless_motor class, parameter passing now done properly * class RControl_In created. Inputs now routed to new pins, can now use two chans both class RControl_In and Servo out - (buggery board required for new inputs) + (buggery board required for new inputs on June 2018 issue boards) * Added version string * Error handler written and included * Realised Nanotec motors are 6 pole, all others are 8 pole. Modified 'mode' to include 'motor poles' in EEPROM data, now speed reading correct for all * Reorganised EEPROM data - mode setting now much easier, less error prone - * Maximum speed now one EEPROM option, range 1.0 to 25.0 MPH + * Maximum speed now one EEPROM option, range 1.0 to 25.0 MPH in 0.1 MPH steps New 29th May 2018 ** LMT01 temperature sensor routed to T1 - and rerouted to PC_13 as InterruptIn on T1 (ports A and B I think) not workable + March 2019 temp sensor only included with TEMP_SENSOR_ENABLE defined. Temp reading not essential, LMT01 was not a good choice due to + significant loading of interrupts, threatening integrity of Real Time System */ @@ -34,7 +39,7 @@ ____________________________________________________________________________________ CONTROL PHILOSOPHY -This Bogie drive board software should ensure sensible control when commands supplied are not sensible ! +This STM3_ESC Bogie drive board software should ensure sensible control when commands supplied are not sensible ! That is, smooth transition between Drive, Coast and Brake to be implemented here. The remote controller must not be relied upon to deliver sensible command sequences. @@ -49,17 +54,21 @@ #if defined (TARGET_NUCLEO_F401RE) // CPU in 64 pin LQFP #include "F401RE.h" // See here for warnings about Servo InterruptIn not working #endif +#if defined (TARGET_NUCLEO_F411RE) // CPU in 64 pin LQFP +#include "F411RE.h" // See here for warnings about Servo InterruptIn not working +#endif #if defined (TARGET_NUCLEO_F446ZE) // CPU in 144 pin LQFP #include "F446ZE.h" // A thought for future version #endif /* Global variable declarations */ -char const const_version_string[] = {"1.0.0\0"}; // Version string, readable from serial ports +char const_version_string[] = {"1.0.y2019.m09.d29\0"}; // Version string, readable from serial ports volatile uint32_t fast_sys_timer = 0; // gets incremented by our Ticker ISR every VOLTAGE_READ_INTERVAL_US -int WatchDog = WATCHDOG_RELOAD + 80; // Allow extra few seconds at powerup +//int WatchDog = WATCHDOG_RELOAD + 80; // Allow extra few seconds at powerup +int WatchDog = 0; // Set this up in main once pre-flight checks done. Allow extra few seconds at powerup bool WatchDogEnable = false; // Must recieve explicit instruction from pc or controller to enable uint32_t volt_reading = 0, // Global updated by interrupt driven read of Battery Volts driverpot_reading = 0, // Global updated by interrupt driven read of Drivers Pot - sys_timer = 0, // gets incremented by our Ticker ISR every MAIN_LOOP_REPEAT_TIME_US + sys_timer = 0, // gets incremented by our Ticker ISR every MAIN_LOOP_REPEAT_TIME_US, 31250us at Sept 2019 AtoD_Semaphore = 0; bool loop_flag = false; // made true in ISR_loop_timer, picked up and made false again in main programme loop @@ -67,15 +76,24 @@ bool temp_sensor_exists = false; double rpm2mph; +double Current_Scaler_Sep_2019 = 1.0; // New idea - Sept 2019. Plan is to scale down motor current limit when voltage lower than nom. + // See schematic for full details, but cycle-by-cycle current limit has the effect of allowing larger average I + // at lower voltages. This is simply because current takes longer to build in motor inductance when voltage + // is low. Conversely, at high supply voltages, motor current reaches limit quickly, cutting drive, meaning + // similar current flows for shorter times at higher voltages. + +#ifdef TEMP_SENSOR_ENABLE uint32_t temp_sensor_count = 0, // incremented by every rising edge from LMT01 last_temperature_count = 0; // global updated approx every 100ms after each LMT01 conversion completes +#endif /* End of Global variable declarations */ Ticker tick_vread; // Device to cause periodic interrupts, used to time voltage readings etc Ticker loop_timer; // Device to cause periodic interrupts, used to sync iterations of main programme loop +#ifdef TEMP_SENSOR_ENABLE Ticker temperature_find_ticker; Timer temperature_timer; - +#endif #ifdef USING_DC_MOTORS Timer dc_motor_kicker_timer; Timeout motors_restore; @@ -127,6 +145,7 @@ extern cli_2019 pcc, tsc; // command line interpreters from pc and touch screen // Interrupt Service Routines +#ifdef TEMP_SENSOR_ENABLE void ISR_temperature_find_ticker () // every 960 us, i.e. slightly faster than once per milli sec { static bool readflag = false; @@ -139,7 +158,7 @@ if (t == 6) readflag = false; } - +#endif /** void ISR_loop_timer () * This ISR responds to Ticker interrupts at a rate of (probably) 32 times per second (check from constant declarations above) * This ISR sets global flag 'loop_flag' used to synchronise passes around main programme control loop. @@ -164,6 +183,7 @@ fast_sys_timer++; // Just a handy measure of elapsed time for anything to use } +#ifdef TEMP_SENSOR_ENABLE void temp_sensor_isr () // got rising edge from LMT01. ALMOST CERTAIN this misses some { int t = temperature_timer.read_us (); // Must be being overrun by something, most likely culprit A-D reading ? @@ -173,7 +193,7 @@ temp_sensor_count++; // T2 = !T2; // scope hanger } - +#endif // End of Interrupt Service Routines @@ -281,6 +301,24 @@ } +void Update_Current_Scaler () { // ***NEW Sept 2019*** Called at 8Hz +const double Vnom = 48.0, + Vmin = Vnom / 3.0, + Voff = Vnom / 4.0; + + double v = Read_BatteryVolts (); + if (v > Vnom) + v = Vnom; + if (v < Voff) + v = Voff; + if (v > Vmin) { // In expected normal operating voltage range + Current_Scaler_Sep_2019 = v / Vnom; // May need to revisit law + } + else { // In very low voltage region + Current_Scaler_Sep_2019 = 0.5 * (v / Vnom); + } +} + void mode_set_both_motors (int mode, double val) // called from cli to set fw, re, rb, hb { MotorA.set_mode (mode); @@ -356,7 +394,7 @@ const int buflen = sizeof(dirbuf) / sizeof(int); const double Pot_Brake_Range = 0.35; //pow (0.5, SERVOIN_PWR_BENDER); //0.353553 for SERVOIN_PWR_BENDER = 1.5; - direction_old = direction_new; + direction_old = direction_new; // Test for change in direction switch setting. // If whole buffer NEWLY filled with all Fwd or all Rev, state = brake_wait @@ -460,16 +498,16 @@ int main() { int eighth_sec_count = 0; - double servo_angle = 0.0; // For testing servo outs - - Temperature_pin.fall (&temp_sensor_isr); - Temperature_pin.mode (PullUp); // Setup system timers to cause periodic interrupts to synchronise and automate volt and current readings, loop repeat rate etc tick_vread.attach_us (&ISR_voltage_reader, VOLTAGE_READ_INTERVAL_US); // Start periodic interrupt generator loop_timer.attach_us (&ISR_loop_timer, MAIN_LOOP_REPEAT_TIME_US); // Start periodic interrupt generator +#ifdef TEMP_SENSOR_ENABLE temperature_find_ticker.attach_us (&ISR_temperature_find_ticker, 960); + Temperature_pin.fall (&temp_sensor_isr); + Temperature_pin.mode (PullUp); + temperature_timer.start (); +#endif // Done setting up system interrupt timers - temperature_timer.start (); pc.baud (9600); // COM port to pc com3.baud (1200); // Once had an idea to use this for IR comms, never tried @@ -495,8 +533,10 @@ // T5 = 0; now input from fw/re on remote control box T6 = 0; +#ifdef TEMP_SENSOR_ENABLE if ((last_temperature_count > 160) && (last_temperature_count < 2400)) // in range -40 to +100 degree C temp_sensor_exists = true; +#endif #ifdef USING_DC_MOTORS dc_motor_kicker_timer.start (); #endif @@ -514,6 +554,10 @@ // pc.printf ("SystemCoreClock=%d, MAX_PWM_TICKS=%d\r\n", SystemCoreClock, MAX_PWM_TICKS); // pcc.test () ; // tsc.test () ; + + WatchDog = WATCHDOG_RELOAD + 80; // Allow extra few seconds at powerup + + while (1) { // Loop forever, repeats synchroised by waiting for ticker Interrupt Service Routine to set 'loop_flag' true while (!loop_flag) { // Most of the time is spent in this loop, repeatedly re-checking for commands from pc port pcc.core () ; // Proceed beyond here once loop_timer ticker ISR has set loop_flag true @@ -522,8 +566,8 @@ } // 32Hz original setting for loop repeat rate loop_flag = false; // Clear flag set by ticker interrupt handler. WHEN LAST CHECKED this was about every 32ms - RC_chan_1.validate_rx(); - RC_chan_2.validate_rx(); + RC_chan_1.validate_rx(); // Tests for pulse width and repetition rates being believable signal from radio control + RC_chan_2.validate_rx(); // bool return values not noted here, these 2 lines are pointless. switch (mode.rd(COMM_SRC)) { // Look to selected source of driving command, act on commands from wherever case 0: // Invalid @@ -574,14 +618,14 @@ LED = !LED; // Toggle LED on board, should be seen to fast flash if (WatchDogEnable) { WatchDog--; - if (WatchDog == 0) { // Deal with WatchDog timer timeout here + if (WatchDog < 1) { // Deal with WatchDog timer timeout here + WatchDog = 0; setVI (0.0, 0.0); // set motor volts and amps to zero // com2.printf ("TIMEOUT %c\r\n", mode.rd(BOARD_ID)); // Potential problem of multiple units reporting at same time overcome by adding board number to WATCHDOG_RELOAD pc.printf ("TIMEOUT %c\r\n", mode.rd(BOARD_ID)); // Brute touch screen controller can do nothing with this } // End of dealing with WatchDog timer timeout - if (WatchDog < 0) - WatchDog = 0; } + Update_Current_Scaler (); eighth_sec_count++; if (eighth_sec_count > 6) { // Send some status info out of serial port every second and a bit or thereabouts eighth_sec_count = 0; @@ -593,8 +637,9 @@ pc.printf ("Temp %.2f\r\n", tmprt); }*/ } -#define SERVO_OUT_TEST +//#define SERVO_OUT_TEST #ifdef SERVO_OUT_TEST + static double servo_angle = 0.0; // For testing servo outs // servo out test here December 2018 servo_angle += 0.01; if (servo_angle > TWOPI)