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:
16:d1e4b9ad3b8b
Parent:
15:2591e2008323
Child:
17:cc9b854295d6
--- a/main.cpp	Sat Nov 30 18:40:30 2019 +0000
+++ b/main.cpp	Tue Jun 09 09:20:19 2020 +0000
@@ -1,7 +1,7 @@
 /*
     STM3_ESC    Electronic Speed Controller board, drives Two Brushless Motors, full Four Quadrant Control.
     Jon Freeman  B. Eng Hons
-    2015 - 2019
+    2015 - 2020
 */
 #include "mbed.h"
 #include "STM3_ESC.h"
@@ -10,11 +10,13 @@
 #include "Servo.h"
 #include "brushless_motor.h"
 #include "Radio_Control_In.h"
-//#ifdef  TARGET_NUCLEO_F401RE    //
-
+#include "LM75B.h"              //  New I2C temp sensor code March 2020 (to suit possible next board issue, harmless otherwise)
+//#ifdef  TARGET_NUCLEO_F401RE    //    Target is TARGET_NUCLEO_F401RE for all boards produced.
 //#endif
 /*
 Brushless_STM3_ESC board
+Apr 2020    *   RC tested on 'Idle Halt' branch line - all good - also tested Inverter Gen power sorce. All good.
+Dec 2019    *   Radio control inputs now fully implemented, requires fitting tiny 'RC_in_fix' 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
@@ -29,6 +31,7 @@
                 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
+        *** New sensor code for LM75B temp sensor added March 2020 ***
 */
 
 
@@ -39,7 +42,7 @@
 
 ____________________________________________________________________________________
         CONTROL PHILOSOPHY
-This STM3_ESC Bogie drive board software should ensure sensible control when commands supplied are not sensible !
+This STM3_ESC Dual Brushless Motor 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.
@@ -51,63 +54,58 @@
 
 */
 
-#if defined (TARGET_NUCLEO_F401RE)  //  CPU in 64 pin LQFP
+//#if defined (TARGET_NUCLEO_L476RG)  //  CPU in 64 pin LQFP  ** NOT PROVED ** No good, pinmap different
+//#include    "F401RE.h"
+//#endif
+#if defined (TARGET_NUCLEO_F401RE)  //  CPU in 64 pin LQFP  -   This is what all produced boards have
 #include    "F401RE.h"
 #endif
-#if defined (TARGET_NUCLEO_F411RE)  //  CPU in 64 pin LQFP
+#if defined (TARGET_NUCLEO_F411RE)  //  CPU in 64 pin LQFP  -   Never tried, but probably would work as is
 #include    "F411RE.h"
 #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_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    = 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
+
+//volatile    uint32_t    fast_sys_timer      = 0;    //  gets incremented by our Ticker ISR every VOLTAGE_READ_INTERVAL_US
+uint32_t    WatchDog            = 0,    //  Set this up in main once pre-flight checks done. Allow extra few seconds at powerup
+            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, 31250us at Sept 2019
             AtoD_Semaphore      = 0;
 
+bool        WatchDogEnable  = false;    //  Must recieve explicit instruction from pc or controller to enable
 bool        loop_flag   = false;        //  made true in ISR_loop_timer, picked up and made false again in main programme loop
 bool        flag_8Hz    = false;        //  As loop_flag but repeats 8 times per sec
-bool        temp_sensor_exists = false;
-double      rpm2mph;
+bool        temp_sensor_exists = false; //  March 2020 - Now used to indicate presence or not of LM75B i2c temp sensor
 
-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.
-
+/*
+    *   Not used since LMT01 proved not a good choice. See LM75B code added March 2020
+    *
 #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;
-#endif
+*/
+/*  End of Global variable declarations */
 
-RControl_In     RC_chan_1   (PC_14);
-RControl_In     RC_chan_2   (PC_15);   //  Instantiate two radio control input channels and specify which InterruptIn pin
+Ticker  tick_vread;     //  Device to cause periodic interrupts, used to time voltage readings etc - 50 us
+Ticker  loop_timer;     //  Device to cause periodic interrupts, used to sync iterations of main programme loop
+
+eeprom_settings             user_settings    (SDA_PIN, SCL_PIN)  ;   //  This MUST come before Motors setup
+RControl_In                 RC_chan_1   (PC_14);
+RControl_In                 RC_chan_2   (PC_15);   //  Instantiate two radio control input channels and specify which InterruptIn pin
 error_handling_Jan_2019     ESC_Error    ;         //  Provides array usable to store error codes.
 
-eeprom_settings     mode    (SDA_PIN, SCL_PIN)  ;   //  This MUST come before Motors setup
+//PCT2075 temp_sensor( i2c );    //  or LM75B temp_sensor( p?, p? );  Added March 2020
+PCT2075 temp_sensor( SDA_PIN, SCL_PIN );    //  or LM75B temp_sensor( p?, p? );  Added March 2020
 
-//uint32_t    HAtot = 0, HBtot = 0, A_Offset = 0, B_Offset = 0;
+
 /*
-    5   1   3   2   6   4  SENSOR SEQUENCE
+    5   1   3   2   6   4  Brushless Motor Hall SENSOR SEQUENCE
 
 1   1   1   1   0   0   0  ---___---___ Hall1
 2   0   0   1   1   1   0  __---___---_ Hall2
@@ -115,36 +113,39 @@
 
     UV  WV  WU  VU  VW  UW  OUTPUT SEQUENCE
 
-8th July 2018
-Added drive to DC brushed motors.
-Connect U and W to dc motor, leave V open.
-
-Hall codes 0 and 7 not used for brushless motors. Without Hall sensors connected pullup resistors give code 7. Use this for dc motors
+Dec 2019 Support for DC motors deleted.
+Hall codes 0 and 7 not used for brushless motors. Without Hall sensors connected pullup resistors give code 7.
 
 */
-const   uint16_t    A_tabl[] = {  //  Origial table
-    0,  0,  0,  0,  0,  0,  0,  0,  //  Handbrake
-    0,  AWHVL,AVHUL,AWHUL,AUHWL,AUHVL,AVHWL,AUHWL,  //  Forward 0, WV1, VU1, WU1, UW1, UV1, VW1, 0,    //  JP, FR, SG, PWM = 1 0 1 1   Forward1
-    0,  AVHWL,AUHVL,AUHWL,AWHUL,AVHUL,AWHVL,AWHUL,  //  Reverse 0, VW1, UV1, UW1, WU1, VU1, WV1, 0,    //  JP, FR, SG, PWM = 1 1 0 1   Reverse1
-    0,  BRA,BRA,BRA,BRA,BRA,BRA,BRA,   //  Regenerative Braking
+const   uint16_t    A_tabl[] = {  //  Table of motor energisation bit patterns. Rows are Handbrake, Forward, Reverse, Regen brake. Cols relate to Hall sensor outputs
+//        AUHVL|AWL,  AWHUL|AVL,  AWHVL|AUH,  AVHWL|AUL,  AUHWL|AVH,  AVHUL|AWH
+    0,  AUHVL|AWL,  AWHUL|AVL,  AWHVL|AUH,  AVHWL|AUL,  AUHWL|AVH,  AVHUL|AWH,    0,  //  Handbrake
+//      1->3        2->6        3->2        4->5        5->1        6->4
+    0,  AWHVL,      AVHUL,      AWHUL,      AUHWL,      AUHVL,      AVHWL,  0,  //  Forward 0, WV1, VU1, WU1, UW1, UV1, VW1, 0,
+//      1->5        2->3        3->1        4->6        5->4        6->2
+    0,  AVHWL,      AUHVL,      AUHWL,      AWHUL,      AVHUL,      AWHVL,  0,  //  Reverse 0, VW1, UV1, UW1, WU1, VU1, WV1, 0,
+    0,  BRA,        BRA,        BRA,        BRA,        BRA,        BRA,    BRA,   //  Regenerative Braking
     KEEP_L_MASK_A, KEEP_H_MASK_A   //  [32 and 33]
 }   ;
-
-const   uint16_t    B_tabl[] = {
-    0,  0,  0,  0,  0,  0,  0,  0,  //  Handbrake
-    0,  BWHVL,BVHUL,BWHUL,BUHWL,BUHVL,BVHWL,BUHWL,  //  Forward 0, WV1, VU1, WU1, UW1, UV1, VW1, 0,    //  JP, FR, SG, PWM = 1 0 1 1   Forward1
-    0,  BVHWL,BUHVL,BUHWL,BWHUL,BVHUL,BWHVL,BWHUL,  //  Reverse 0, VW1, UV1, UW1, WU1, VU1, WV1, 0,    //  JP, FR, SG, PWM = 1 1 0 1   Reverse1
-    0,  BRB,BRB,BRB,BRB,BRB,BRB,BRB,   //  Regenerative Braking
+//        AUHVL|AWL,  AWHUL|AVL,  AWHVL|AUH,  AVHWL|AUL,  AUHWL|AVH,  AVHUL|AWH
+const   uint16_t    B_tabl[] = {    //  Different tables for Motors A and B as different ports and different port bits used.
+//    0,    0,      0,      0,      0,      0,      0,    0,  //  Handbrake
+    0,  BUHVL|BWL,  BWHUL|BVL,  BWHVL|BUH,  BVHWL|BUL,  BUHWL|BVH,  BVHUL|BWH,    0,  //  Handbrake
+    0,  BWHVL,      BVHUL,      BWHUL,      BUHWL,      BUHVL,      BVHWL,    0,  //  Forward 0, WV1, VU1, WU1, UW1, UV1, VW1, 0,
+    0,  BVHWL,      BUHVL,      BUHWL,      BWHUL,      BVHUL,      BWHVL,  0,  //  Reverse 0, VW1, UV1, UW1, WU1, VU1, WV1, 0,
+    0,  BRB,        BRB,        BRB,        BRB,        BRB,        BRB,    BRB,   //  Regenerative Braking
     KEEP_L_MASK_B, KEEP_H_MASK_B
 }   ;
 
 brushless_motor   MotorA  (MOT_A_I_ADC, APWMV, APWMI, A_tabl, _MAH1, _MAH2, _MAH3, PortA, PORT_A_MASK, ISHUNTA);
-brushless_motor   MotorB  (MOT_B_I_ADC, BPWMV, BPWMI, B_tabl, _MBH1, _MBH2, _MBH3, PortB, PORT_B_MASK, ISHUNTB);
-
+brushless_motor   MotorB  (MOT_B_I_ADC, BPWMV, BPWMI, B_tabl, _MBH1, _MBH2, _MBH3, PortB, PORT_B_MASK, ISHUNTB);    //  Two brushless motor instantiations
 
 extern  cli_2019    pcc, tsc;   //  command line interpreters from pc and touch screen
 
+static const int    LM75_I2C_ADDR = 0x90;   //  i2c temperature sensor
+
 //  Interrupt Service Routines
+/*
 #ifdef  TEMP_SENSOR_ENABLE
 void    ISR_temperature_find_ticker ()      //  every 960 us, i.e. slightly faster than once per milli sec
 {
@@ -158,13 +159,25 @@
     if  (t == 6)
         readflag = false;
 }
+
+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 ?
+    temperature_timer.reset ();
+    temp_sensor_count++;
+    if  (t > 18)            //  Yes proved some interrupts get missed, this fixes temperature reading
+        temp_sensor_count++;
+//    T2 = !T2;   //  scope hanger
+}
 #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.
 *   Increments global 'sys_timer', usable anywhere as general measure of elapsed time
 */
-void    ISR_loop_timer  ()      //  This is Ticker Interrupt Service Routine - loop timer - MAIN_LOOP_REPEAT_TIME_US
+void    ISR_loop_timer  ()      //  This is Ticker Interrupt Service Routine - loop timer - MAIN_LOOP_REPEAT_TIME_US (32Hz)
 {
     loop_flag = true;   //  set flag to allow main programme loop to proceed
     sys_timer++;        //  Just a handy measure of elapsed time for anything to use
@@ -180,31 +193,41 @@
 void    ISR_voltage_reader  ()      //  This is Ticker Interrupt Service Routine ; few us between readings ; VOLTAGE_READ_INTERVAL_US    = 50
 {
     AtoD_Semaphore++;
-    fast_sys_timer++;        //  Just a handy measure of elapsed time for anything to use
+//    fast_sys_timer++;        //  Just a handy measure of elapsed time for anything to use
+}
+
+//  End of Interrupt Service Routines
+
+const char *    get_version    ()  {
+    return  "1.0.y2020.m05.d21\0";  //  Version string, readable using 'ver' serial command
 }
 
-#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 ?
-    temperature_timer.reset ();
-    temp_sensor_count++;
-    if  (t > 18)            //  Yes proved some interrupts get missed, this fixes temperature reading
-        temp_sensor_count++;
-//    T2 = !T2;   //  scope hanger
+bool    read_temperature    (float & t) {
+//    pc.printf   ("test param temp = %7.3f\r\n", t);
+    if  (!temp_sensor_exists)
+        return  false;
+    t = temp_sensor;
+    return  true;
 }
-#endif
-//  End of Interrupt Service Routines
 
-
-void    setVI   (double v, double i)
+void    setVI_A   (double v, double i)
 {
     MotorA.set_V_limit  (v);  //  Sets max motor voltage
     MotorA.set_I_limit  (i);  //  Sets max motor current
+}
+
+void    setVI_B   (double v, double i)
+{
     MotorB.set_V_limit  (v);
     MotorB.set_I_limit  (i);
 }
 
+void    setVI_both   (double v, double i)
+{
+    setVI_A (v, i);
+    setVI_B (v, i);
+}
+
 
 /**
 *   void    AtoD_reader ()  //  Call to here every VOLTAGE_READ_INTERVAL_US    = 50 once loop responds to flag set in isr
@@ -217,11 +240,11 @@
         MotorA.high_side_off    ();
     if  (MotorB.tickleon)
         MotorB.high_side_off    ();
-    if  (AtoD_Semaphore > 20)   {
+    if  (AtoD_Semaphore > 10)   {
         pc.printf   ("WARNING - sema cnt %d\r\n", AtoD_Semaphore);
-        AtoD_Semaphore = 20;
+        AtoD_Semaphore = 10;
     }
-    while   (AtoD_Semaphore > 0)    {
+    while   (AtoD_Semaphore > 0)    {   //  semaphore gets incremented in timer interrupt handler, t=50us
         AtoD_Semaphore--;
         //  Code here to sequence through reading voltmeter, demand pot, ammeters
         switch  (i) {   //
@@ -254,36 +277,6 @@
     }
 }
 
-/** double  Read_Servo1_In  ()
-*   driverpot_reading is a global 16 bit unsigned int updated in interrupt service routine
-*   ISR also filters signal
-*   This function returns normalised double (range 0.0 to 1.0) representation of driver pot position
-*/
-double  Read_Servo1_In  ()
-{
-    const double    xjoin   = 0.5,
-                    yjoin   = 0.35,
-                    slope_a = yjoin / xjoin,
-                    slope_b = (1.0 - yjoin)/(1.0 - xjoin);
-//                    centre = 0.35,  //  For pot, first (1/3)ish in braking area, position = 1/3 drift, > (1/3)ish drive
-//                    halfish = 0.5;  //  RC stick in the centre value
-                    //  Bottom half of RC stick movement maps to lowest (1/3)ish pot movement
-                    //  Higher half of RC stick movement maps to highest (2/3)ish pot movement
-    double  t;
-    double  demand = RC_chan_1.normalised();
-    //  apply demand = 1.0 - demand here to swap stick move polarity
-//    return  pow (demand, SERVOIN_PWR_BENDER);
-    if  (demand < 0.0)  demand = 0.0;
-    if  (demand > 1.0)  demand = 1.0;
-    if  (demand < xjoin) {
-        demand *= slope_a;
-    }
-    else    {
-        t = yjoin + ((demand - xjoin) * slope_b);
-        demand = t;
-    }
-    return  demand;
-}
 
 /** double  Read_DriverPot  ()
 *   driverpot_reading is a global 16 bit unsigned int updated in interrupt service routine
@@ -301,71 +294,26 @@
 }
 
 
-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);
-    }
+double  trim    (const double min, const double max, double input)  {
+    if  (input < min)   input = min;
+    if  (input > max)   input = max;
+    return  input;
 }
 
-void    mode_set_both_motors   (int mode, double val)      //  called from cli to set fw, re, rb, hb
+void    brake_motors_both    (double brake_effort)  {
+    MotorA.brake    (brake_effort);
+    MotorB.brake    (brake_effort);
+}
+
+
+void    mode_set_motors_both   (int mode)      //  called from cli to set fw, re, rb, hb
 {
     MotorA.set_mode (mode);
     MotorB.set_mode (mode);
-    if  (mode == MOTOR_REGENBRAKE)    {
-        if  (val > 1.0)
-            val = 1.0;
-        if  (val < 0.0)
-            val = 0.0;
-        val *= 0.9;    //  set upper limit, this is essential
-        val = sqrt  (val);  //  to linearise effect
-        setVI  (val, 1.0);
-    }
 }
 
 
-
-#ifdef  USING_DC_MOTORS
-void    restorer    ()
-{
-    motors_restore.detach   ();
-    if  (MotorA.dc_motor)   {
-        MotorA.set_V_limit  (MotorA.last_V);
-        MotorA.set_I_limit  (MotorA.last_I);
-        MotorA.motor_set    ();
-    }
-    if  (MotorB.dc_motor)   {
-        MotorB.set_V_limit  (MotorB.last_V);
-        MotorB.set_I_limit  (MotorB.last_I);
-        MotorB.motor_set    ();
-    }
-}
-#endif
-
-void    rcin_report ()  {
-    double c1 = RC_chan_1.normalised();
-    double c2 = RC_chan_2.normalised();
-    uint32_t    pc1 = RC_chan_1.pulsecount();
-    uint32_t    pc2 = RC_chan_2.pulsecount();
-    pc.printf   ("At rcin_report, Ch1=%.3f, Ch2=%.3f, cnt1 %d, cnt2 %d\r\n", c1, c2, pc1, pc2);
-//    if  (c1 < -0.0001)
-        pc.printf   ("\t1 period %d, pulsewidth %d\r\n", RC_chan_1.period(), RC_chan_1.pulsewidth());
-//    if  (c2 < -0.0001)
-        pc.printf   ("\t2 period %d, pulsewidth %d\r\n", RC_chan_2.period(), RC_chan_2.pulsewidth());
-}
-
-bool    worth_the_bother    (double a, double b)    {   //  Tests size of change. No point updating tiny demand changes
+bool    is_it_worth_the_bother    (double a, double b)    {   //  Tests size of change. No point updating tiny demand changes
     double c = a - b;
     if  (c < 0.0)
         c = 0.0 - c;
@@ -374,9 +322,9 @@
     return  false;
 }
 
-void    hand_control_state_machine  (int source)  {     //  if hand control mode '3', get here @ approx 30Hz to read drivers control pot
-                                                        //  if servo1 mode '4', reads input from servo1 instead
-enum    {   //  states used in hand_control_state_machine()
+void    hand_control_state_machine  (int source)  {     //  if hand control user_settings '3', get here @ approx 30Hz to read drivers control pot
+                                                        //  if servo1 user_settings '4', reads input from servo1 instead
+enum    {   //  states used in RC_or_hand_control_state_machine()
         HAND_CONT_IDLE,
         HAND_CONT_BRAKE_WAIT,
         HAND_CONT_BRAKE_POT,
@@ -392,7 +340,7 @@
     static  int dirbuf[5] = {100,100,100,100,100};      //  Initialised with invalid direction such that 'change' is read from switch
     static  int dirbufptr = 0, direction_old = -1, direction_new = -1;
     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;
+    double      User_Brake_Range;  //
 
    direction_old = direction_new;
 
@@ -410,10 +358,9 @@
         hand_controller_state = HAND_CONT_BRAKE_WAIT;   //  validated change of direction switch
 
     switch  (source)    {
-        case    3:  //  driver pot is control input
+        case    HAND:  //  driver pot is control input
             pot_position = Read_DriverPot   ();     //  Only place read in the loop, rteads normalised 0.0 to 1.0
-            break;
-        case    4:  //  servo 1 input is control input
+            User_Brake_Range = user_settings.user_brake_range();
             break;
         default:
             pot_position = 0.0;
@@ -427,14 +374,14 @@
         case    HAND_CONT_BRAKE_WAIT:   //  Only get here after direction input changed or newly validated at power on
             pc.printf   ("At HAND_CONT_BRAKE_WAIT\r\n");
             brake_effort = 0.05;    //  Apply braking not too fiercely
-            mode_set_both_motors    (MOTOR_REGENBRAKE, brake_effort);  //  Direction change 
+            brake_motors_both   (brake_effort);
             hand_controller_state = HAND_CONT_BRAKE_POT;
             break;
 
         case    HAND_CONT_BRAKE_POT:        //  Only get here after one pass through HAND_CONT_BRAKE_WAIT but
-            if  (brake_effort < 0.9)    {   //   remain in this state until driver has turned pott fully anticlockwise
+            if  (brake_effort < 0.95)    {   //   remain in this state until driver has turned pott fully anticlockwise
                 brake_effort += 0.05;   //  ramp braking up to max over about one sec after direction change or validation
-                mode_set_both_motors    (MOTOR_REGENBRAKE, brake_effort);  //  Direction change or testing at power on
+                brake_motors_both    (brake_effort);  //  Direction change or testing at power on
                 pc.printf   ("Brake effort %.2f\r\n", brake_effort);
             }
             else    {   //  once braking up to quite hard
@@ -445,24 +392,23 @@
             break;
 
         case    HAND_CONT_STATE_INTO_BRAKING:
-            brake_effort = (Pot_Brake_Range - pot_position) / Pot_Brake_Range;
+            brake_effort = (User_Brake_Range - pot_position) / User_Brake_Range;
             if  (brake_effort < 0.0)
                 brake_effort = 0.5;
-            mode_set_both_motors    (MOTOR_REGENBRAKE, brake_effort);
+            brake_motors_both    (brake_effort);
             old_pot_position = pot_position;
             hand_controller_state = HAND_CONT_STATE_BRAKING;
             pc.printf   ("Brake\r\n");
             break;
 
         case    HAND_CONT_STATE_BRAKING:
-            if  (pot_position > Pot_Brake_Range)    //  escape braking into drive
+            if  (pot_position > User_Brake_Range)    //  escape braking into drive
                 hand_controller_state = HAND_CONT_STATE_INTO_DRIVING;
             else    {
-                if  (worth_the_bother(pot_position, old_pot_position))  {
+                if  (is_it_worth_the_bother(pot_position, old_pot_position))  {
                     old_pot_position = pot_position;
-                    brake_effort = (Pot_Brake_Range - pot_position) / Pot_Brake_Range;
-                    mode_set_both_motors    (MOTOR_REGENBRAKE, brake_effort);
-//                    pc.printf   ("Brake %.2f\r\n", brake_effort);
+                    brake_effort = (User_Brake_Range - pot_position) / User_Brake_Range;
+                    brake_motors_both    (brake_effort);
                 }
             }
             break;
@@ -470,20 +416,20 @@
         case    HAND_CONT_STATE_INTO_DRIVING:   //  Only get here after HAND_CONT_STATE_BRAKING
             pc.printf   ("Drive\r\n");
             if  (direction_new == 1)
-                mode_set_both_motors   (MOTOR_FORWARD, 0.0);  //  sets both motors
+                mode_set_motors_both   (MOTOR_FORWARD);  //  sets both motors
             else
-                mode_set_both_motors   (MOTOR_REVERSE, 0.0);
+                mode_set_motors_both   (MOTOR_REVERSE);
             hand_controller_state = HAND_CONT_STATE_DRIVING;
             break;
 
         case    HAND_CONT_STATE_DRIVING:
-            if  (pot_position < Pot_Brake_Range)    //  escape braking into drive
+            if  (pot_position < User_Brake_Range)    //  escape braking into drive
                 hand_controller_state = HAND_CONT_STATE_INTO_BRAKING;
             else    {
-                if  (worth_the_bother(pot_position, old_pot_position))  {
+                if  (is_it_worth_the_bother(pot_position, old_pot_position))  {
                     old_pot_position = pot_position;
-                    drive_effort = (pot_position - Pot_Brake_Range) / (1.0 - Pot_Brake_Range);
-                    setVI  (drive_effort, drive_effort);   //  Wind volts and amps up and down together ???
+                    drive_effort = (pot_position - User_Brake_Range) / (1.0 - User_Brake_Range);
+                    setVI_both  (drive_effort, drive_effort);   //  Wind volts and amps up and down together ???
                     pc.printf   ("Drive %.2f\r\n", drive_effort);
                 }
             }
@@ -495,68 +441,81 @@
     }       //  endof switch  (hand_controller_state) {
 }
 
-int main()
+void    set_RCIN_offsets    ()  {
+    RC_chan_1.set_offset    (user_settings.rd(RCI1_TRIM), user_settings.rd(RCIN_REGBRAKE_RANGE), user_settings.rd(RCIN_STICK_ATTACK));
+    RC_chan_2.set_offset    (user_settings.rd(RCI2_TRIM), user_settings.rd(RCIN_REGBRAKE_RANGE), user_settings.rd(RCIN_STICK_ATTACK));
+    RC_chan_1.set_chanmode  (user_settings.rd(RCIN1), user_settings.rd(RCIN1REVERSE))  ;
+    RC_chan_2.set_chanmode  (user_settings.rd(RCIN2), user_settings.rd(RCIN2REVERSE))  ;
+}
+
+
+int main()  //  Programme entry point
 {
-    int eighth_sec_count = 0;
+    uint32_t    eighth_sec_count = 0;
     //  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
+    tick_vread.attach_us    (&ISR_voltage_reader, VOLTAGE_READ_INTERVAL_US);    //  Start periodic interrupt generator - 50 us
     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
 
-    pc.baud     (9600);     //  COM port to pc
     com3.baud   (1200);     //  Once had an idea to use this for IR comms, never tried
     com2.baud   (19200);    //  Opto isolated serial bus connecting 'n' STM3_ESC boards to 1 Brute Touch Screen controller
+//    pc.baud     (9600);     //  COM port to pc
+    pc.baud     (user_settings.baud());     //  COM port to pc
 
-    rpm2mph = 60.0                                                          //  to Motor Revs per hour;
-              * ((double)mode.rd(MOTPIN) / (double)mode.rd(WHEELGEAR))  //  Wheel revs per hour
-              * PI * ((double)mode.rd(WHEELDIA) / 1000.0)                  //  metres per hour
-              * 39.37 / (1760.0 * 36.0);                                      //  miles per hour
+//    pc.printf   ("Baud rate %d\r\n", user_settings.baud());
 
-    MotorA.direction_set    (mode.rd(MOTADIR));     //  modes all setup in class   eeprom_settings {}  mode    ; constructor
-    MotorB.direction_set    (mode.rd(MOTBDIR));
-    MotorA.poles            (mode.rd(MOTAPOLES));   //  Returns true if poles 4, 6 or 8. Returns false otherwise
-    MotorB.poles            (mode.rd(MOTBPOLES));   //  Only two calls are here
-    MotorA.set_mode         (MOTOR_REGENBRAKE);
-    MotorB.set_mode         (MOTOR_REGENBRAKE);
-    setVI  (0.9, 0.5);              //  Power up with moderate regen braking applied
+    MotorA.set_direction    (user_settings.rd(MOTADIR));     //  user_settingss all setup in class   eeprom_settings {}  user_settings    ; constructor
+    MotorB.set_direction    (user_settings.rd(MOTBDIR));
+    MotorA.poles            (user_settings.rd(MOTAPOLES));   //  Returns true if poles 4, 6 or 8. Returns false otherwise
+    MotorB.poles            (user_settings.rd(MOTBPOLES));   //  Only two calls are here
+//    MotorA.set_mode         (MOTOR_REGENBRAKE);   //  Done in motor constructor
+//    MotorB.set_mode         (MOTOR_REGENBRAKE);
+//    setVI_both  (0.9, 0.5);              //  Power up with moderate regen braking applied
+    set_RCIN_offsets    ();
+
+    class  RC_stick_info   RCstick1, RCstick2;
 
 //    T1 = 0;   Now WRONGLY hoped to be InterruptIn counting pulses from LMT01 temperature sensor
     T2 = 0; //  T2, T3, T4 As yet unused pins
-//    T3 = 0;
-//    T4 = 0;
-//    T5 = 0; now input from fw/re on remote control box
+//    T3 = 0;//    T4 = 0;//    T5 = 0; now input from fw/re on remote control box
     T6 = 0;
+    pc.printf   ("\r\n\nSTM3_ESC Starting Ver %s, Command Source setting = %d\r\n", get_version(), user_settings.rd(COMM_SRC));
+    pc.printf   ("Designed by Jon Freeman  B. Eng. Hons - 2017-2020. e jon@jons-workshop.com\r\n");
+    if  (user_settings.do_we_have_i2c  (0xa0))
+        pc.printf   ("Got EEPROM, i2c error count = %d\r\n", user_settings.errs());
+    else
+        pc.printf   ("No eeprom found\r\n");
+    pc.printf   ("ESC_Error.all_good() ret'd %s\r\n", ESC_Error.all_good() ? "true - Ready to Roll !" : "false");
 
-#ifdef  TEMP_SENSOR_ENABLE
-    if  ((last_temperature_count > 160) && (last_temperature_count < 2400))   //  in range -40 to +100 degree C
+//bool    eeprom_settings::do_we_have_i2c  (uint32_t x)
+    pc.printf   ("LM75 temp sensor ");
+    if  (user_settings.do_we_have_i2c  (LM75_I2C_ADDR))    {
+        pc.printf   ("reports temperature %7.3f\r\n", (float)temp_sensor );
         temp_sensor_exists  = true;
-#endif
-#ifdef  USING_DC_MOTORS
-    dc_motor_kicker_timer.start   ();
-#endif
-    int old_hand_controller_direction = T5;
-    if  (mode.rd(COMM_SRC) == 3)  {      //  Read fwd/rev switch 'T5', PA15 on 401
+    }
+    else
+        pc.printf   ("Not Fitted\r\n");
+
+    uint32_t    old_hand_controller_direction = T5;
+    if  (user_settings.rd(COMM_SRC) == 3)  {      //  Read fwd/rev switch 'T5', PA15 on 401
         pc.printf   ("Pot control\r\n");
         if  (T5)
-            mode_set_both_motors   (MOTOR_FORWARD, 0.0);  //  sets both motors
+            mode_set_motors_both   (MOTOR_FORWARD);  //  sets both motors
         else
-            mode_set_both_motors   (MOTOR_REVERSE, 0.0);
+            mode_set_motors_both   (MOTOR_REVERSE);
     }
-
-    pc.printf   ("About to start %s!, mode_bytes[COMM_SRC]= %d\r\n", const_version_string, mode.rd(COMM_SRC));
-    pc.printf   ("ESC_Error.all_good() ret'd %s\r\n", ESC_Error.all_good() ? "true" : "false");
 //    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
-
+    pcc.flush   (); //  Flush serial rx buffers
+    tsc.flush   ();
+//    pc.printf   ("sizeof int is %d\r\n", sizeof(int));  //  sizeof int is 4
+    double      Current_Scaler;  //  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.
 
     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
@@ -566,62 +525,57 @@
         }                       //  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
 
-        switch  (mode.rd(COMM_SRC))  {   //  Look to selected source of driving command, act on commands from wherever
-            case    0:  //  Invalid
-                break;
-            case    COM1:  //  COM1    -   Primarily for testing, bypassed by command line interpreter
-                break;
-            case    COM2:  //  COM2    -   Primarily for testing, bypassed by command line interpreter
+            //  double  trim    (const double min, const double max, double input)  {
+        Current_Scaler = trim  (0.1, 1.0, Read_BatteryVolts() / user_settings.Vnom());
+        MotorA.I_scale  (Current_Scaler);   //  Reduces motor current limits when voltage below nominal.
+        MotorB.I_scale  (Current_Scaler);   //  This goes some way towards preventing engine stalls perhaps
+
+        MotorA.speed_monitor_and_control   ();   //  Needed to keep table updated to give reading in Hall transitions per second
+        MotorB.speed_monitor_and_control   ();   //  Read MotorX.PPS to read pulses per sec or MotorX.RPM to read motor RPM
+
+        RC_chan_1.read(RCstick1);   //  Get latest info from Radio Control inputs
+        RC_chan_2.read(RCstick2);
+
+//#define SERVO_OUT_TEST
+#ifdef  SERVO_OUT_TEST
+        if  (RCstick1.active)   Servo1 = RCstick1.deflection;
+        if  (RCstick2.active)   Servo2 = RCstick2.deflection;
+#endif
+
+        switch  (user_settings.rd(COMM_SRC))  {   //  Look to selected source of driving command, act on commands from wherever
+//            case    0:  //  Invalid
+//                break;
+//            case    COM1:  //  COM1    -   Primarily for testing, bypassed by command line interpreter
+//                break;
+            case    COM2:  //  COM2    -   Nothing done here, all serial instructions handled in command line interpreter
                 break;
             case    HAND:  //  Put all hand controller input stuff here
-                hand_control_state_machine  (3);
+                hand_control_state_machine  (HAND);
                 break;  //  endof hand controller stuff
             case    RC_IN1:  //  RC_chan_1
-                hand_control_state_machine  (4);
+                RC_chan_1.energise  (RCstick1, MotorA)  ;   //  RC chan 1 drives both motors
+                RC_chan_1.energise  (RCstick1, MotorB)  ;
                 break;
             case    RC_IN2:  //  RC_chan_2
+                RC_chan_2.energise  (RCstick2, MotorA)  ;   //  RC chan 2 drives both motors
+                RC_chan_2.energise  (RCstick2, MotorB)  ;
+                break;
+            case    RC_IN_BOTH:  //  Robot Mode
+                RC_chan_1.energise  (RCstick1, MotorA)  ;   //  Two RC chans drive two motors independently
+                RC_chan_2.energise  (RCstick2, MotorB)  ;
                 break;
             default:
                 if  (ESC_Error.read(FAULT_UNRECOGNISED_STATE))  {
-                    pc.printf   ("Unrecognised state %d\r\n", mode.rd(COMM_SRC));    //  set error flag instead here
+                    pc.printf   ("Unrecognised state %d\r\n", user_settings.rd(COMM_SRC));    //  set error flag instead here
                     ESC_Error.set   (FAULT_UNRECOGNISED_STATE, 1);
                 }
                 break;
-        }   //  endof   switch  (mode_bytes[COMM_SRC])  {
-
-#ifdef  USING_DC_MOTORS
-        dc_motor_kicker_timer.reset   ();
-#endif
-        MotorA.speed_monitor_and_control   ();   //  Needed to keep table updated to give reading in Hall transitions per second
-        MotorB.speed_monitor_and_control   ();   //  Read MotorX.PPS to read pulses per sec or MotorX.RPM to read motor RPM
+        }   //  endof   switch  (user_settings_bytes[COMM_SRC])  {
 
-#ifdef  USING_DC_MOTORS
-        if  (MotorA.dc_motor)   {
-//            MotorA.raw_V_pwm    (1);
-            MotorA.low_side_on  ();
-        }
-        if  (MotorB.dc_motor)   {
-//            MotorB.raw_V_pwm    (1);
-            MotorB.low_side_on  ();
-        }
-        if  (MotorA.dc_motor || MotorB.dc_motor)    {
-//            motors_restore.attach_us    (&restorer, ttime);
-            motors_restore.attach_us    (&restorer, 25);
-        }
-#endif
-#define SERVO_OUT_TEST
-#ifdef  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
-
-        //  Tests for pulse width and repetition rates being believable signal from radio control
-        if  (RC_chan_1.validate_rx())   
-            Servo1 = RC_chan_1.normalised();
-        if  (RC_chan_2.validate_rx())   
-            Servo2 = RC_chan_2.normalised();
-//            RC_chan_2.validate_rx();
-
-
 /*
         servo_angle += 0.01;
         if  (servo_angle > TWOPI)
@@ -630,31 +584,23 @@
         Servo2 = ((cos(servo_angle)+1.0) / 2.0);
 */
         //  Yep!    Both servo outs work lovely December 2018
-#endif
+//#endif
 
         if  (flag_8Hz)  {   //  do slower stuff
             flag_8Hz    = false;
-            LED = !LED;                   // Toggle LED on board, should be seen to fast flash
+            LED = !LED;                   // Toggle green LED on board, should be seen to fast flash
             if  (WatchDogEnable)    {
                 WatchDog--;
                 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
+                    setVI_both  (0.0, 0.0);  //  set motor volts and amps to zero
+                    pc.printf ("TIMEOUT %c\r\n", user_settings.rd(BOARD_ID));   //  Brute touch screen controller can do nothing with this
                 }                       //  End of dealing with WatchDog timer timeout
             }
-            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;
                 ESC_Error.report_any (false);   //  retain = false - reset error after reporting once
-                /*                if  (temp_sensor_exists)    {
-                                    double  tmprt = (double) last_temp_count;
-                                    tmprt /= 16.0;
-                                    tmprt -= 50.0;
-                                    pc.printf   ("Temp %.2f\r\n", tmprt);
-                                }*/
             }
         }   //  End of if(flag_8Hz)
     }       //  End of main programme loop