Code for 'Smart Regulator' featured in 'Model Engineer', November 2020 on. Contains all work to August 2020 including all code described. Top level algorithm development is quite spares, leaving some work for you! Any questions - jon@jons-workshop.com

Dependencies:   mbed BufferedSerial Servo2 PCT2075 I2CEeprom FastPWM

Files at this revision

API Documentation at this revision

Comitter:
JonFreeman
Date:
Mon Jul 27 08:44:59 2020 +0000
Parent:
2:8e7b51353f32
Child:
4:28cc0cf01570
Commit message:
End of July after good day out at Ashton Court

Changed in this revision

Alternator.h Show annotated file Show diff for this revision Revisions of this file
I2CEeprom.lib Show annotated file Show diff for this revision Revisions of this file
PCT2075.lib Show annotated file Show diff for this revision Revisions of this file
baro.cpp Show annotated file Show diff for this revision Revisions of this file
baro.h Show annotated file Show diff for this revision Revisions of this file
cli.cpp Show annotated file Show diff for this revision Revisions of this file
field.cpp Show annotated file Show diff for this revision Revisions of this file
field.h Show annotated file Show diff for this revision Revisions of this file
gps_mod.cpp Show annotated file Show diff for this revision Revisions of this file
gps_mod.h Show annotated file Show diff for this revision Revisions of this file
i2c_2020_06.cpp Show annotated file Show diff for this revision Revisions of this file
i2c_bit_banged.cpp Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
rpm.cpp Show annotated file Show diff for this revision Revisions of this file
rpm.h Show annotated file Show diff for this revision Revisions of this file
--- a/Alternator.h	Mon Jun 08 13:46:52 2020 +0000
+++ b/Alternator.h	Mon Jul 27 08:44:59 2020 +0000
@@ -1,57 +1,64 @@
-#include    "Servo.h"
-#include    "BufferedSerial.h"
 
-//#define SPEED_CONTROL_ENABLE    //  Includes engine revs servo control loop
+//#define GPS_  //  Not the crap one I tried!
 
-const   uint32_t    TICKOVER_RPM = 2500;
-const   uint32_t    PWM_OFF_RPM_LIMIT = (TICKOVER_RPM * 9) / 10;
-const   uint32_t    MAX_RPM_LIMIT = 7500;
-const   double      MAX_FIELD_PWM = 0.47;
-const   double      SERVO_MAX = 0.5;
-const   double      DRIVER_NEUTRAL = 0.18;
-const   uint32_t    eeprom_page = 17;   //  Determines where in eeprom 'settings' reside
+const   double      ALTERNATOR_DESIGN_VOLTAGE = 14.0;   //  Used to scale down max field pwm when available voltage higher than this
+const   double      DRIVER_NEUTRAL      = 0.18;  //  Proportion of driver's pot travel deemed to be zero power request
+const   uint32_t    eeprom_page         = 17;   //  Determines where in eeprom 'settings' reside
+const   int         eeprom_page_size    = 32;
+const   int         PWM_PERIOD_US       = 1800    ;   //  Was 2400, May want to reduce this, note would require change of resistor value on board
 
-//const   int lut_seg_size    =   60; //  steps per thousand RPM
-//const   int lut_size = lut_seg_size * 8;    //  8 segments - 0-1, 1-2, 2-3, 3-4 etc 000 rpm
+enum    {TABL0, TABL1, TABL2, TABL3, TABL4, TABL5, TABL6, TABL7, TABL8, TABL9, TABL10,
+        TABL11, TABL12, TABL13, TABL14, TABL15, TABL16, TABL17, TABL18, TABL19, TABL20,   
+        WARM_UP_DELAY, WARMUP_SERVO_POS, OP_MODE, SPEED_CTRL_P, SERVO_DIR, FUT27, FUT28, FUT29, FUT30, FUT31 }  ;
 
-class   VEXT_Data   {
-    public:
-    uint64_t    t_on, t_off, measured_pw_us, measured_period, rise_count, fall_count;
-    double      duty_cycle  ()  {
-        return  (double) measured_pw_us / (double) measured_period;
-    }   ;
-    VEXT_Data   ()  {   //  constructor
-        t_on = t_off = measured_pw_us =  measured_period = rise_count = fall_count = 0L;
-    }   ;
+struct  sldandt  {
+    const uint32_t    min, max, de_fault;  //  min, max, default
+    const char * txt;     //  description
+    char    val;
 }   ;
 
-class   eeprom_settings {
-    char        settings    [36];
+
+class   ee_settings_2020    {
+    char    new_settings[eeprom_page_size + 2];
   public:
-    eeprom_settings (); //  Constructor
-    char        rd  (uint32_t)  ;           //  Read one setup char value from private buffer 'settings'
-    bool        rd  (char *, 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        load    ()  ;               //  Get 'settings' from EEPROM
-    bool        set_defaults    ();         //  Put default settings into EEPROM and local buffer
-//    uint32_t    errs    ()  ;               //  Return errors
-}   ;
-
-enum    {RPM0, RPM1, RPM2, RPM3, RPM4, RPM5, RPM6, RPM7, RPM8, 
-            PWM_SCALE, FUT1, FUT2, FUT3, FUT4, FUT5}  ;  //  
-
-struct  optpar  {
-    int min, max, def;  //  min, max, default
-    const char * t;     //  description
+    ee_settings_2020    ()  ;   //  Constructor
+    int load    ()  ;
+    int save    ()  ;
+    char    rd  (uint32_t   i)  ;
+    bool    wr  (char c, uint32_t i)  ;           //  Write one setup char value to private buffer 'settings'
+    sldandt *    inform (uint32_t which)   ;
 }   ;
 
 const   int MAX_PARAMS = 12;    //  Up from 10 May 2020
-struct  parameters  {
-    int32_t times[50];
-    int32_t position_in_list, last_time, numof_dbls;
+struct  parameters  {   //  Used in Command Line Interpreter, stores user input values
+    int32_t position_in_list, numof_dbls;
     double  dbl[MAX_PARAMS];
 }   ;
 
-const   int PWM_PERIOD_US = 1800    ;   //  Was 2400, May want to reduce this, note would require change of resistor value on board
 
+/*
+- Position the Cursor:
+  \033[<L>;<C>H
+     Or
+  \033[<L>;<C>f
+  puts the cursor at line L and column C.
+- Move the cursor up N lines:
+  \033[<N>A
+- Move the cursor down N lines:
+  \033[<N>B
+- Move the cursor forward N columns:
+  \033[<N>C
+- Move the cursor backward N columns:
+  \033[<N>D
+
+- Clear the screen, move to (0,0):
+  \033[2J
+- Erase to end of line:
+  \033[K
+
+- Save cursor position:
+  \033[s    might not work
+- Restore cursor position:
+  \033[u    might not work
+*/
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/I2CEeprom.lib	Mon Jul 27 08:44:59 2020 +0000
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/rhourahane/code/I2CEeprom/#b23f5561266c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PCT2075.lib	Mon Jul 27 08:44:59 2020 +0000
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/okano/code/PCT2075/#0cdfb3ea5969
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/baro.cpp	Mon Jul 27 08:44:59 2020 +0000
@@ -0,0 +1,176 @@
+#ifdef  INC_BARO
+
+#include "mbed.h"
+#include "baro.h"
+#include "BufferedSerial.h"
+
+//BufferedSerial  pc          (PA_2, PA_3, 512, 4, NULL);    //  Pins 16, 17    tx, rx to pc via usb lead
+//I2C i2c                     (PB_7, PB_6);   //  Pins 58, 59 For 24LC64 eeprom
+extern  BufferedSerial  pc;
+extern  I2C i2c;
+const int ACK     = 1;
+const int NAK     = 0;
+const int NOM_PASCALS   = 100000;
+const int HYSTERESIS_PASCALS = 50;
+
+MPL3115A2::MPL3115A2    ()  {   //  Constructor
+        good = true;
+//        mode = 0x39;  //  continuous convert
+        mode = 0x38;    //  one shot
+        Temp = Pres = Alti = Adju = 0.0;
+        if  (!RegRd (0x0c, 1, dest))
+            good = false;
+        else if (dest[0] != 196)
+            good = false;
+}
+
+bool    MPL3115A2::busy_check  ()  {
+    char    c;
+    RegRd   (0x26, 1, &c);
+    c &= 2;
+    if  (c == 0)
+        return  false;  //  free, i.e. not busy
+    return  true;       //  busy, conversion in progress
+}
+
+void    MPL3115A2::reset_zero_inHg  ()  {
+    double tmp;
+    Adju = 0.0;
+    tmp = inHg_vacuum (); //  sets Adju
+    Adju = tmp;
+}
+double  MPL3115A2::Temperature ()  {    return  Temp;   }
+double  MPL3115A2::Pressure    ()  {    return  Pres;   }
+double  MPL3115A2::Altitude    ()  {    return  Alti;   }
+double  MPL3115A2::inHg_vacuum ()  {
+    double  inHg = 0.0 - Pres;   //  one atmospheres worth of Pascals. Note 1 bar is not the same, 1 bar = 100000 Pa
+    inHg *= 0.000295299833;
+    return  inHg - Adju;
+}
+void    MPL3115A2::mode_alti   ()  {
+//    mode = 0xb9;
+    mode = 0xb8;
+}
+void    MPL3115A2::mode_pres   ()  {
+//    mode = 0x39;
+    mode = 0x38;
+}
+/*********************************************************\
+* IIC Write Register
+\*********************************************************/
+bool MPL3115A2::RegWr  (int reg, char val)
+{
+    bool    rv = true;
+    i2c.start   ();
+    if  (i2c.write   (0xc0)     != ACK) rv = false;  //  Returns '0' - NAK was received '1' - ACK was received, '2' - timeout
+    if  (i2c.write   (reg)      != ACK) rv = false;
+    if  (i2c.write   (val)      != ACK) rv = false;
+    i2c.stop    ();
+    return  rv;
+}
+
+/*********************************************************\
+* IIC Read One Or More Registers into array
+\*********************************************************/
+bool MPL3115A2::RegRd  (int reg1, int len, char *array)
+{
+    int acknak;
+    bool    rv = true;
+    i2c.start   ();
+    if  (i2c.write   (0xc0) != ACK)     rv = false;  //  Returns '0' - NAK was received '1' - ACK was received, '2' - timeout
+    if  (i2c.write   (reg1) != ACK)     rv = false;
+    i2c.start   ();             // Send Repeat Start
+    if  (i2c.write   (0xc1) != ACK)     rv = false;
+    acknak = ACK;
+    while (len > 0) {                                 // Read Register Values
+        len--;
+        if  (len == 0)    acknak = NAK;
+        *array++ = i2c.read    (acknak);
+    }
+    i2c.stop    ();
+    return  rv;
+}
+
+bool    MPL3115A2::Update    ()   {   //  Returns true on apparent success
+    bool    rv = true;
+    int32_t ta;
+    if  (busy_check())  {   //  "User shall read the value of the OST bit before writing to this bit again." - done here.
+        pc.printf   ("Call to Barometer Update while busy error\r\n");
+        return  false;  //  Can not initiate new conversion, will not update Temp, Pres, Alti
+    }
+    Pres = Alti = -1.0; //  Gives added clue if call to Update fails
+    if  (!RegRd (1, 5, dest))       rv = false;    //  Read 3 pressure + 2 temperature registers, then kick-start new conversion
+    if  (!RegWr (0x26, mode | 2))   rv = false;    //  bit 1 is OST one shot mode, bits 3-5 are 128x oversampling. This starts new conversion
+    if  (!rv)
+        return  false;  //  register read or write failed, Pres and Alti = -1.0 also
+    Temp = ((double) ((dest[3] << 4) | (dest[4] >> 4))) / 16.0; //  Temperature
+    if  (mode & 0x80)  {    //  Altimeter mode
+        ta = (dest[0] << 24) | (dest[1] << 16) | ((dest[2] & 0xf0) << 8);   //  preserve sign bit
+        ta /= (1 << 12);
+        Alti = ((double) ta) / 16.0;
+        Pres = 0.0;
+    }
+    else    {   //  Pressure mode
+        Pres = ((double) ((dest[0] << 12) | (dest[1] << 4) | (dest[2] >> 4))) / 4.0;
+        Alti = 0.0;
+    }
+    return  true;
+}
+
+
+/*
+int main()
+{
+    double  Pres, Alt, inHg, Heat;
+    i2c.frequency(400000);      //  Speed 400000 max.
+    class   MPL3115A2   baro    ;
+    int q;      //  Note address bits 3-1 to match addr pins on 24LC16 memory device
+    for (int i = 0; i < 255; i += 2)    {   //  Search for devices at all possible i2c addresses
+        i2c.start();
+        wait_ms (1);
+        q = i2c.write(i);   //  may return error code 2 when no start issued
+        switch  (q) {
+            case    ACK:
+                pc.printf   ("I2C device found at 0x%x\r\n", i);
+            case    NAK:      //  Device not seen at this address
+                break;
+            case    2:      //  write reports timeout
+                pc.printf   ("I2C Timeout at addr %2x\r\n", i);
+                break;
+            default:
+                pc.printf   ("Unknown error %d in check_24LC64\r\n", q);
+                break;
+        }
+    }
+    i2c.stop();
+    baro.mode_pres  ();
+    pc.printf   ("baro good flag %s\r\n", baro.good ? "true":"false");
+    bool    rv;
+    q = -3;
+    while(1) {
+        q++;
+        if  (q == 0)    baro.reset_zero_inHg();
+        myled = 1; // LED is ON
+        wait(0.3); // 200 ms
+//        pc.printf   ("busy_check %s\r\n", baro.busy_check() ? "true":"false");
+        myled = 0; // LED is OFF
+        wait(0.8); // 1 sec
+//        pc.printf   ("busy_check %s\r\n", baro.busy_check() ? "true":"false");
+        rv  = baro.Update ();
+        Pres = baro.Pressure();
+        Alt = baro.Altitude();
+        inHg = baro.inHg_vacuum();
+        Heat = baro.Temperature();
+const int NOM_PASCALS   = 100000;
+const int HYSTERESIS_PASCALS = 50;
+        if  (Pres > (NOM_PASCALS + HYSTERESIS_PASCALS))
+            pump = 1;
+        if  (Pres < (NOM_PASCALS - HYSTERESIS_PASCALS))
+            pump = 0;
+//        pc.printf   ("Pres\t%.2f Pa\tAlt\t%.1f m\t%+000.1f inHg\tTemp\t%.1f C, %s\r\n", baro.Pressure(), baro.Altitude(), baro.inHg_vacuum (), baro.Temperature(), rv ? "true":"false");
+        pc.printf   ("Pres\t%.2f Pa\tAlt\t%.1f m\t%+000.1f inHg\tTemp\t%.1f C, %s\r\n", Pres, Alt, inHg, Heat, rv ? "true":"false");
+//        rv  = baro.Update ();
+    }
+}
+*/
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/baro.h	Mon Jul 27 08:44:59 2020 +0000
@@ -0,0 +1,21 @@
+class   MPL3115A2   {   //  Barometer, pressure sensor, thermometer
+    static const int ps_wr = 0xc0;  //  set bit 0 for read, clear bit 0 for write
+    char    mode,
+            dest[8];
+    bool    RegWr  (int reg, char val)    ;
+    bool    RegRd  (int reg1, int len, char *array)   ;
+    double  Temp, Pres, Alti, Adju, inHg;
+    public :
+    bool    good;
+    MPL3115A2   ()  ;   //  Constructor
+    bool    Update      ()  ;   //  Returns true on apparent success
+    bool    busy_check  ()  ;
+    void    reset_zero_inHg  ()  ;
+    void    mode_alti   ()  ;
+    void    mode_pres   ()  ;
+    double  Temperature ()  ;
+    double  Pressure    ()  ;
+    double  Altitude    ()  ;
+    double  inHg_vacuum ()  ;
+}   ;
+
--- a/cli.cpp	Mon Jun 08 13:46:52 2020 +0000
+++ b/cli.cpp	Mon Jul 27 08:44:59 2020 +0000
@@ -5,38 +5,62 @@
     Also usable as comms subsystem in finished code for accepting commands, reporting data etc.
 */
 #include "mbed.h"
+#include "field.h"
 #include "Alternator.h"
-//#include "BufferedSerial.h"
+#include "BufferedSerial.h"
 #include <cctype>
 using namespace std;
 
-extern  eeprom_settings     user_settings     ;
-//eeprom_settings     mode     ;
+ee_settings_2020 user_settings     ;
 
-//extern  int ver, vef, measured_pw_us;
-extern  void    set_throttle_limit    (struct parameters & a)    ;
-//extern  void    speed_control_factor_set    (struct parameters & a)    ;
+extern  BufferedSerial pc;
+extern  void    maketable   ()  ;
 extern  void    query_system    (struct parameters & a)    ;
 extern  uint32_t    ReadEngineRPM  ()   ;
-extern  double  Read_BatteryVolts   ()  ;
-//extern  void Read_Ammeter   (double *)  ;
-
-
-
-
-
-//  WithOUT RTOS
-//extern  BufferedSerial pc;
+extern  double  Read_Link_Volts   ()  ;
+extern  double  Read_Field_Volts   ()  ;
+extern  double  Read_Ammeter        ()  ;
 
-#ifdef  TARGET_NUCLEO_L432KC    //
-extern  Serial pc;
-#else
-extern  BufferedSerial pc;
-#endif
-//extern  double  test_pot;    //  These used in knifeandfork code testing only
-extern  void    maketable   ()  ;
+//bool    ee_settings_2020::wr   (char c, uint32_t i)  {           //  Write one setup char value to private buffer 'settings'
+/*
+void    slope_cmd   (struct parameters & a)   {
+    Provides a quick way of filling lookup table.
+    Sets percent at 3000 RPM with first parameter 0 to 100
+*/
+void    slope_cmd   (struct parameters & a)   {     //  Requires two params. First %@ 3000 rpm, second slope MAX +/-20 % per krpm above
+    const   int startat = 1800; //  rpm to start at
+    const   int rpm_per = 200; //  rpm per lookup table step
+    const   int threshold = startat / rpm_per;
+    signed char    at_power_beyond, slope;
+    pc.printf   ("Slope - set pct = %d @ 3krpm, slope %d pct above\r\n", (int32_t)a.dbl[0], (int32_t)a.dbl[1]);
+    if  (a.numof_dbls != 2) 
+        pc.printf   ("Need 2 params in slope, got %d, ", a.numof_dbls);
+    else    {
+        pc.printf   ("Got slope params %.1f, %.1f\r\n", a.dbl[0], a.dbl[1]);
+        if  (a.dbl[0] > 100.0)  a.dbl[0] = 100.0;
+        if  (a.dbl[0] < 0.0)    a.dbl[0] = 0.0;
+        if  (a.dbl[1] > +20.0)  a.dbl[1] = +20.0;
+        if  (a.dbl[1] < -20.0)  a.dbl[1] = -20.0;
+        at_power_beyond    = (signed char)a.dbl[0];
+        slope   = (signed char)a.dbl[1];
+        pc.printf   ("Setting slope ");
+        for (int i = 0; i < threshold; i++) {   //  Zero all very low speed settings
+            user_settings.wr    (0, i);
+        }
+        for (int i = threshold; i < 21; i++)    {
+            user_settings.wr    (at_power_beyond, i);
+            pc.printf   ("%d, ", at_power_beyond);
+            at_power_beyond += slope;
+            if  (at_power_beyond < 0)      at_power_beyond = 0;
+            if  (at_power_beyond > 100)    at_power_beyond = 100;
+        }
+        pc.printf   ("\r\nDone\r\n");
+        user_settings.save  ();
+        maketable   ();
+    }
+}
 
-void    table_tweak_cmd   (struct parameters & a)   {     //  Requires two params. First '20', '25' etc representing hundreds RPM. Second 0 to 99 percent
+void    table_tweak_cmd   (struct parameters & a)   {     //  Requires two params. First '20', '22' etc representing hundreds RPM. Second 0 to 99 percent
     char    txt[100];
     uint32_t    d[3];
     txt[0] = 0;
@@ -44,9 +68,9 @@
         sprintf   (txt, "Need 2 params, got %d, ", a.numof_dbls);
     else    {
         d[2] = (uint32_t)a.dbl[0];
-        d[0] = d[2] / 5;
+        d[0] = d[2] / 2;
         d[1] = (uint32_t)a.dbl[1];
-        if  (d[0] > 16 || d[1] > 99 || d[2] != d[0] * 5)
+        if  (d[0] > 20 || d[1] > 100 || d[2] != d[0] * 2)
             sprintf (txt + strlen(txt), "Param out of range %d, %d, ", d[2], d[1]);
         else    {
             pc.printf   ("Off to reset table %d RPM, %d percent\r\n", d[2] * 100, d[1]);
@@ -61,115 +85,169 @@
         pc.printf   ("Good in table_tweak_cmd, RPM=%d, percentage=%d\r\n", d[0] * 500, d[1]);
 }
 
-//extern  int numof_eeprom_options2    ;
-//extern  struct  optpar const option_list2[]  ;
-extern  struct optpar  option_list2[]    ;
+
+//extern  VEXT_Data   Field;
+extern  FieldControl   Field;
+
+extern  int32_t set_engine_RPM_lit  (uint32_t   RPMrequest) ;   //  Returns speed error pos or neg
+extern  int32_t set_engine_RPM_pct  (uint32_t   RPMrequest) ;   //  Returns speed error pos or neg
+void    ss_cmd   (struct parameters & a)   {     //  Set engine Speed 0 - 8000 RPM
+    uint32_t    v = (uint32_t) a.dbl[0];
+    pc.printf   ("Setting engine RPM to %d, measured RPM returned = %d\r\n", v, set_engine_RPM_lit  (v));
+}
 
-/**void    mode_cmd (struct parameters & a)       //  With no params, reads eeprom contents. With params sets eeprom contents
-*   mode_cmd called only from pc comms. No sense calling from Touch Screen Controller
-*
-*   Called without parameters - Lists to pc terminal current settings
-*
-*/
-void    mode19_cmd (struct parameters & a)       //  With no params, reads eeprom contents. With params sets eeprom contents
-{
+void    sp_cmd   (struct parameters & a)   {     //  Set engine Speed 0 - 8000 RPM
+    uint32_t    v = (uint32_t) a.dbl[0];
+    pc.printf   ("Setting engine RPM percent to %d, measured RPM returned = %d\r\n", v, set_engine_RPM_pct  (v));
+}
+
+void    rfcmd   (struct parameters & a)   {     //  
+//    pc.printf   ("Field.measured_period = %u", (uint32_t)Field.get_measured_period());
+//    pc.printf   (", Field.measured_pw_us = %u, duty_cycle = %.3f\r\n", (uint32_t)Field.measured_pw_us, Field.duty_cycle());
+    pc.printf   ("Field duty cycle measured = %.3f\r\n", Field.get_duty_ratio());
+}
+
+void    vcmd    (struct parameters & a)   {
+    pc.printf   ("link volts %.2f, field volts %.2f\r\n", Read_Link_Volts(), Read_Field_Volts());
+}
+
+void    acmd    (struct parameters & a)   {
+    pc.printf   ("amps %.2f\r\n", Read_Ammeter());
+}
+
 
-    char            temps[36];
-    int i;
-    pc.printf   ("\r\nmode - Set system data in EEPROM - Jan 2019\r\nSyntax 'mode' with no parameters lists current state.\r\n");
-    if  (a.numof_dbls)  {           //  If more than 0 parameters supplied
-        for (i = 0; i < a.numof_dbls; i++)
-            temps[i] = (char)a.dbl[i];          //  recast doubles to char
-        while   (i < 33)
-            temps[i++] = 0;
-        i = (int)a.dbl[0];
-        switch  (i) {
-            case    0:    case    1:    case    2:    case    3:    case    4:
-            case    5:    case    6:    case    7:    case    8:
-                if  (temps[1] >= option_list2[i].min && temps[1] <= option_list2[i].max)
-                    user_settings.wr(temps[1], RPM0 + i);
-                break;
-            case    37: //  set pwm scale factor
-                if  (temps[1] >= option_list2[PWM_SCALE].min && temps[1] <= option_list2[PWM_SCALE].max)
-                    user_settings.wr(temps[1], PWM_SCALE);
-                break;
-            case    83: //  set to defaults
-                user_settings.set_defaults   ();
-                break;
-            case    9:      //  9 Save settings
-                user_settings.save   ();
-                pc.printf   ("Saving settings to EEPROM\r\n");
-                break;
-            default:
-                break;
-        }       //  endof   switch
-    }           //  endof   //  If more than 0 parameters supplied
-    else    {
-        pc.printf   ("No Changes\r\n");
+extern  void    set_pwm (double)   ;    //  Range 0.0 to 1.0
+void    fls_cmd (struct parameters & a)   {
+    pc.printf   ("Setting field.limiter to %d percent\r\n", (int)a.dbl[0]);
+    set_pwm (a.dbl[0] / 100.0);
+}
+
+void    set_defaults_cmd (struct parameters & a)   {
+    struct  sldandt * p = NULL;
+    bool    flag = true;
+    int i = 0;
+    while   (flag)  {
+        p = user_settings.inform(i);    //  Returns NULL when i goes out of range
+        if  (p == NULL)
+            flag = false;
+        else    {
+            pc.printf   ("min %d, max %d, default %d, text %s\r\n", p->min, p->max, p->de_fault, p->txt);
+            user_settings.wr    (p->de_fault, i);
+            i++;
+        }
     }
-    pc.printf   ("mode 0\t%s, [%d]\r\n", option_list2[0].t, user_settings.rd(RPM0));
-    pc.printf   ("mode 1\t%s, [%d]\r\n", option_list2[1].t, user_settings.rd(RPM1));
-    pc.printf   ("mode 2\t%s, [%d]\r\n", option_list2[2].t, user_settings.rd(RPM2));
-    pc.printf   ("mode 3\t%s, [%d]\r\n", option_list2[3].t, user_settings.rd(RPM3));
-    pc.printf   ("mode 4\t%s, [%d]\r\n", option_list2[4].t, user_settings.rd(RPM4));
-    pc.printf   ("mode 5\t%s, [%d]\r\n", option_list2[5].t, user_settings.rd(RPM5));
-    pc.printf   ("mode 6\t%s, [%d]\r\n", option_list2[6].t, user_settings.rd(RPM6));
-    pc.printf   ("mode 7\t%s, [%d]\r\n", option_list2[7].t, user_settings.rd(RPM7));
-    pc.printf   ("mode 8\t%s, [%d]\r\n", option_list2[8].t, user_settings.rd(RPM8));
+    user_settings.save  ();
+}
 
-    pc.printf   ("mode 37\t%s, [%d]\r\n", option_list2[PWM_SCALE].t, user_settings.rd(PWM_SCALE));
-    pc.printf   ("mode 83\tSet to defaults\r\n");
-    pc.printf   ("mode 9\tSave settings\r\r\n");
-
+void    servodir_cmd (struct parameters & a)   {
+    char    ch = (char)a.dbl[0];
+    if  (a.numof_dbls != 1 || ch > 1) {
+        pc.printf   ("Wrong servodir set\r\n");
+        return  ;
+    }
+    if  (user_settings.rd(SERVO_DIR) != ch) {
+        pc.printf   ("Setting servo dir %.1f  \r\n", a.dbl[0]);
+        user_settings.wr(ch, SERVO_DIR);
+        user_settings.save();
+    }
 }
 
-/*void    gpcmd   (struct parameters & a)   {
-    pc.printf   ("pwm=%.3f\r\n", user_settings.get_pwm    ((int)a.dbl[0]));
-}*/
+char * modes_txt[] =    {
+    "0\tSafe nothing mode for cli cmd testing",
+    "1\tPot to Servo direct, field OFF",
+    "2\tVariable voltage",
+    "3\tFixed voltage",
+    "4\tEngine Revs Control",
+    "5\tSet Engine to Driver's Pot",
+    "6\tControl Engine by Current Load",
+    "7\tAuto Test",
+}   ;
+
+int numof_op_modes = sizeof(modes_txt) / sizeof(char *);
 
-extern  VEXT_Data   Field;
+char *  get_mode_text   (uint32_t mode)  {
+    if  (mode > numof_op_modes)   {
+        pc.printf   ("mode OOR in get_mode_text, %d\r\n", mode);
+        mode = numof_op_modes - 1;
+    }
+    return  modes_txt[mode];
+}
 
-void    rfcmd   (struct parameters & a)   {     //  Note casts all wrong here, values are 64 bit
-    pc.printf   ("Field.measured_period = %u", (uint32_t)Field.measured_period);
-    pc.printf   (", Field.measured_pw_us = %u, duty_cycle = %.3f\r\n", (uint32_t)Field.measured_pw_us, Field.duty_cycle());
+void    mode20_cmd (struct parameters & a)   ;
+void    mode_cmd (struct parameters & a)   {
+    if  (a.numof_dbls == 1 && (uint32_t) a.dbl[0] <= numof_op_modes)  {
+        a.dbl[1] = a.dbl[0];
+        a.dbl[0] = OP_MODE; //23.0;
+        a.numof_dbls = 2;
+        mode20_cmd  (a);
+        return;
+    }
+    pc.printf   ("Current mode is %d  \r\nTo set operating mode, use mode n :- where \r\n", user_settings.rd(OP_MODE));
+    for (int i = 0; i < numof_op_modes; i++)
+        pc.printf   ("%s\r\n", get_mode_text(i));
 }
 
-//extern  void    set_RPM_demand  (uint32_t   d)  ;
-
-/*void    set_rpm_cmd   (struct parameters & a)   {
-    pc.printf   ("setting RPM to %d\r\n",(int)a.dbl[0]);
-    set_RPM_demand  ((uint32_t)a.dbl[0]);
-}*/
-
-/*void    speedcmd   (struct parameters & a)   {
-    int s = ReadEngineRPM  ();
-    pc.printf   ("speed %d, pwm %.3f\r\n", s, user_settings.get_pwm(s));
-}*/
-
-void    vcmd    (struct parameters & a)   {
-    pc.printf   ("volts %.2f\r\n", Read_BatteryVolts());
+void    mode20_cmd (struct parameters & a)   {
+    struct  sldandt * p = NULL;     //  Pointer to struct containing max, min and default values, and brief text descriptor for a command
+    int i = 0;
+    bool    flag = true;
+    bool    save_settings = false;
+    int32_t    cmd_num = (int32_t) a.dbl[0], first_p = (int32_t) a.dbl[1];
+    pc.printf   ("At user setting, numofparams = %d, dbl[0]=%.2f, dbl[1]=%.2f\r\n", a.numof_dbls, a.dbl[0], a.dbl[1]);
+    if  (a.numof_dbls < 2)   {              //  Need at least command number followed by at least one parameter
+        pc.printf   ("Listing Setup\r\nTo alter, enter us param number, new value\r\n");
+        while (flag)    {
+            p = user_settings.inform(i);    //  Returns false when i goes out of range
+            if  (p == NULL)
+                flag = false;
+            else    {
+                pc.printf   ("%d\tval %d, min %d, max %d, default %d, text %s\r\n", i, user_settings.rd(i), p->min, p->max, p->de_fault, p->txt);
+                i++;
+            }
+        }
+        return  ;
+    }       //  When too few parameters, output list. Done.
+    p = user_settings.inform(cmd_num);  //  Set  pointer to min, max, default and text info
+    if  (p == NULL) {
+        pc.printf   ("Invalid command number %d in user setting entry\r\n", cmd_num);
+        return  ;
+    }
+    if  (first_p < p->min || first_p > p->max)  {
+        pc.printf   ("%s\r\nParameter min %d, max %d, you entered %d. Not setting\r\n", p->txt, p->min, p->max, first_p);
+        return  ;
+    }
+    pc.printf   ("Hoping to set [%s] to %d\r\n", p->txt, first_p);
+    switch  (cmd_num)    {
+        case    OP_MODE:    //  
+        case    WARM_UP_DELAY:
+        case    WARMUP_SERVO_POS:
+        case    SPEED_CTRL_P:
+        case    SERVO_DIR:
+            i = user_settings.rd(cmd_num);
+            if  (i == first_p)
+                pc.printf   ("No need, [%s] already set to %d\r\n", p->txt, i);
+            else    {
+                user_settings.wr((char)first_p, cmd_num);
+                save_settings = true;
+                pc.printf   ("Setting [%s] to %d\r\n", p->txt, first_p);
+            }
+            break;
+        default:
+            pc.printf   ("No code for [%s]\r\n", p->txt);
+            break;
+    }
+    if  (save_settings)
+        user_settings.save();
 }
 
-/*void    icmd    (struct parameters & a)   {
-    double  results[4];
-    //double * ampsptr = 
-    Read_Ammeter(results)   ;
-    pc.printf   ("amps %.3f, offset %.3f\r\n", results[0], results[1]);
-}*/
-
-extern  void    set_servo   (double p)  ;   //  Only for test, called from cli
-
-void    set_servo_cmd    (struct parameters & a)   {
-    double p = a.dbl[0] / 100.0;
-    pc.printf   ("servo %.2f\r\n", p);
-    set_servo   (p);
+extern  void    auto_test_initiate  (int bulb_count)  ;
+void    auto_test_kickoff_cmd (struct parameters & a)   {
+    auto_test_initiate  ((int)a.dbl[0]);
 }
 
-extern  bool    set_pwm (double)   ;    //  Range 0.0 to 1.0
-void    p_cmd (struct parameters & a)   {
-//    int32_t    i = (int32_t)a.dbl[0];
-    pc.printf   ("Setting PWM to %d percent\r\n", (int)(a.dbl[0] * 100.0));
-    set_pwm (a.dbl[0]);
+void    test_Fsfs_cmd (struct parameters & a)   {
+    uint32_t    rpm = (uint32_t)a.dbl[0];
+    pc.printf   ("Field.set_for_speed %d returned %d\r\n", rpm, Field.set_for_speed(rpm));
 }
 
 void    null_cmd (struct parameters & a)   {
@@ -186,22 +264,23 @@
 
 struct  kb_command const command_list[] = {
     {"?", "Lists available commands, same as ls", menucmd},
-    {"tt", "Table Tweak - 2 params RPM/100, percentage 0-99", table_tweak_cmd},
+    {"ft", "Test Field.set_for_speed fn", test_Fsfs_cmd},
+    {"at", "Initiate Auto Test sequence", auto_test_kickoff_cmd},
+    {"svod", "Set servo sense 0 or 1", servodir_cmd},
+    {"sd", "Set User Settings Defaults", set_defaults_cmd},
+    {"us", "Set User Settings", mode20_cmd},
+    {"tt", "Table Tweak 0 - 100", table_tweak_cmd},
+    {"ss", "Set Speed 0 - 8000 RPM", ss_cmd},
+    {"sp", "Set Speed 0 - 100 percent", sp_cmd},
+//    {"tl", "Throttle logger tester, enter param 0.0 - 1.0", throt_log_cmd},
     {"rf", "Check rise and fall on VEXT", rfcmd},
-//    {"s", "Speed, RPM", speedcmd},
     {"v", "Read Battery volts", vcmd},
-//    {"i", "Read Ammeter", icmd},
-    {"p", "Set PWM 0 to 2400???", p_cmd},
+    {"i", "Read Ammeter", acmd},
+    {"fl", "Field limiter set 0 to 99 percent", fls_cmd},
+    {"mode", "Set operating mode - as us23", mode_cmd},
+    {"slope", "Field limiter set pct 0 to 99 @3k, slope pct per k above", slope_cmd},
     {"q", "Query system - toggle message stream on/off", query_system},
-//    {"gp","Get pwm from RPM", gpcmd},
-    {"mode", "See or set eeprom values", mode19_cmd},
     {"nu", "do nothing", null_cmd},
-#ifndef SPEED_CONTROL_ENABLE    //  Includes engine revs servo control loop
-    {"ser","set throttle servo direct 0 - 99", set_servo_cmd},
-#endif
-//    {"sf","set speed control factor", speed_control_factor_set},
-//    {"sv","set engine RPM demand 2500 - 6000", set_rpm_cmd},
-    {"tl","set throttle_limit 0.0-1.0", set_throttle_limit},
 };
 
 const int numof_menu_items = sizeof(command_list) / sizeof(kb_command);
@@ -249,7 +328,7 @@
                             param_block.dbl[k] = 0.0;
                         }
                         param_block.position_in_list = i;
-                        param_block.last_time = clock    ();
+                        //param_block.last_time = clock    ();
                         param_block.numof_dbls = 0;
                         pEnd = cmd_line + wrdlen;
                         while   (*pEnd)  {          //  Assemble all numerics as doubles
@@ -261,7 +340,7 @@
                         pc.printf   ("\r\n");
 //                        for (int k = 0; k < param_block.numof_dbls; k++)
 //                            pc.printf   ("Read %.3f\r\n", param_block.dbl[k]);
-                        param_block.times[i] = clock();
+//                        param_block.times[i] = clock();
                         command_list[i].f(param_block);   //  execute command
                         i = numof_menu_items + 1;    //  to exit for loop
                     }   //  end of match found
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/field.cpp	Mon Jul 27 08:44:59 2020 +0000
@@ -0,0 +1,105 @@
+#include "mbed.h"
+#include "rpm.h"
+#include "field.h"
+#include "Alternator.h"
+#include "BufferedSerial.h"
+extern  BufferedSerial pc;
+
+extern  ee_settings_2020 user_settings;
+extern  Timer   microsecs;      //  64 bit counter, rolls over in half million years
+
+FieldControl::FieldControl  (PinName pwmoscin, PinName vext) : pwm_osc_in (pwmoscin), V_ext (vext) {
+    pwm_osc_in.period_us      (PWM_PERIOD_US); //  about 313Hz * 2
+    pwm_osc_in.pulsewidth_us  (PWM_PERIOD_US - 2);
+    V_ext.rise  (callback(this, &FieldControl::VextRise));     // Attach handler to the rising interruptIn edge
+    V_ext.fall  (callback(this, &FieldControl::VextFall));     // Attach handler to the falling interruptIn edge
+    t_on = t_off = measured_pw_us =  measured_period = rise_count = fall_count = 0L;
+//    maketable   ()  ;
+    set_for_speed   (0);
+    old_percent = 1000; //  i.e. out of percentage range to force exec on first pass
+}
+
+void    FieldControl::VextRise    ()  //  InterruptIn interrupt service
+{   //  Here is possible to read back how regulator has controlled pwm - may or may not be useful
+    uint64_t    tmp = microsecs.read_high_resolution_us();
+    measured_period = tmp - t_on;
+    t_on = tmp;
+    rise_count++;
+}
+
+void    FieldControl::VextFall    ()  //  InterruptIn interrupt service
+{
+    fall_count++;
+    t_off = microsecs.read_high_resolution_us();
+    measured_pw_us = t_off - t_on;
+}
+
+double  FieldControl::get_duty_ratio  ()  {
+    if  (measured_period == 0)  {
+        if  (V_ext == 1)
+            return  2.0;    //  Impossible value indicates error
+        return  -1.0;       //  Impossible value indicates error
+    }
+    return  (double) measured_pw_us / (double) measured_period;
+}   ;
+
+
+void    FieldControl::maketable   ()  {   //  Uses first 17 nums of user_settings relating to lim to be applied at 0, 500, 1000 --- 8000 RPM
+//  Makes table of 400 entries to 8000 RPM regardless of engine type data.  20 RPM per fine step
+//  21 User input table entries 200 RPM apart for MAX_RPM_LIMIT up to 4000, 400 RPM for up to 8000
+    double      tabvals[28];
+    double      diff, val = 0.0;
+    uint32_t    tabptr = 0, user_entry_rpm_step, divor;
+    user_entry_rpm_step = MAX_RPM_LIMIT > 4000 ? 400 : 200;
+    divor               = MAX_RPM_LIMIT > 4000 ? 20 : 20;
+    for (int i = 0; i < 21; i++)    {
+        tabvals[i] = (double)user_settings.rd   (i);
+        pc.printf   ("%d\t%.0f\r\n", i * user_entry_rpm_step, tabvals[i]);
+    }
+    for (int i = 1; i < 21; i++)    {
+        diff = tabvals[i] - tabvals[i - 1];     //  Either 200 or 400 RPM apart, either 10 or 20 small steps to fill
+        diff /= divor;   //  50 entries 20RPM apart per kRPM
+        for (int j = 0; j < divor; j++)    {
+            privatemadetab[tabptr++] = (uint8_t) val;
+            val += diff;
+        }
+    }
+    pc.printf   ("Final tab entry posn %d, calculated = %d percent, divor = %d\r\n", tabptr, (uint8_t) val, divor);
+    while   (tabptr < sizeof(privatemadetab))
+        privatemadetab[tabptr++] = (uint8_t) val;
+}
+
+uint32_t    FieldControl::set_for_speed   (uint32_t    rpm)  {       //  May 2020
+    uint32_t    index, pcent;
+    double  pwm = 0.0;
+    if  (rpm > MAX_RPM_LIMIT)
+        rpm = MAX_RPM_LIMIT;
+//    index = rpm / 25;  //  to fit lut spacing of 25rpm intervals, turns rpm into index
+    index = rpm / 10;  //  to fit lut spacing of 10rpm intervals, turns rpm into index
+    pcent = privatemadetab[index];
+    if  (pcent != old_percent) {
+        old_percent = pcent;
+        pwm = (double)pcent;
+        pwm /= 100.0;
+        set_pwm (pwm);
+    }
+    return  old_percent;
+}
+
+extern  double  Read_Field_Volts   ()   ;
+
+void    FieldControl::set_pwm (double d)   {
+    double  t = Read_Field_Volts    ()  ;
+    if  (t > ALTERNATOR_DESIGN_VOLTAGE)
+        t = ALTERNATOR_DESIGN_VOLTAGE / t;
+    else
+        t = 1.0;
+    uint32_t i;
+    if  (d < 0.0)
+        d = 0.0;
+    if  (d > 1.0)
+        d = 1.0;
+    i = (uint32_t)(d * t * (double)PWM_PERIOD_US);   //  pwm_factor 0.5 when using 12v alternator in 24v system
+    pwm_osc_in.pulsewidth_us  (PWM_PERIOD_US - i);            //  Note PWM is inverted as MCP1630 uses inverted OSC_IN signal
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/field.h	Mon Jul 27 08:44:59 2020 +0000
@@ -0,0 +1,32 @@
+#include "mbed.h"
+/**
+    Functions to control alternator field
+*/
+
+
+/**void    set_pwm (double d)   {   Range 0.0 to 1.0
+    This PWM used to limit max duty ratio of alternator field energisation.
+    With R25=33k and C4=100n controlling ramp input to CS pin of MCP1630 (not MCP1630V),
+    ramp terminates fet 'on' pulse after a max of approx 980 us.
+    With const   int PWM_PERIOD_US = 2000    , duty ratio is thus limited to approx 50% max.
+    This is about right when using 12V alternator on 24V systems
+    A 1.225V reference (U7) is fed to the MCP1630 error amp which compares this to fed-back proportion of system voltage.
+    This adjusts final PWM down to zero % as needed to maintain alternator output voltage.
+*/
+
+class   FieldControl    {
+    uint8_t     privatemadetab[440];
+    uint64_t    t_on, t_off, measured_pw_us, measured_period, rise_count, fall_count;
+    uint32_t    old_percent;
+    PwmOut      pwm_osc_in;     //  Controller PWM driving MCP1630
+    InterruptIn V_ext;          //  Connected to MCP1630 output to MOSFET
+    void        VextRise ();    //  Handles - MCP1630 has just turned mosfet on
+    void        VextFall ();    //  Handles - MCP1630 has just turned mosfet off
+  public:
+    FieldControl  (PinName pwmoscin, PinName vext)  ;   //  Constructor
+    void    set_pwm (double);   //  What it says, 0.0 to 1.0 but inverts to suit MCP1630
+    void    maketable   ();
+    uint32_t    set_for_speed   (uint32_t rpm);
+    double  get_duty_ratio  ()  ;
+}   ;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gps_mod.cpp	Mon Jul 27 08:44:59 2020 +0000
@@ -0,0 +1,265 @@
+#include "mbed.h"
+
+#ifdef GPS_
+
+#include "mbed.h"
+#include "gps_mod.h"
+using namespace std;
+#if defined  (TARGET_NUCLEO_L476RG)  //  Computer is low power ARM Cortex development system
+#include "BufferedSerial.h"
+    BufferedSerial gps_module   (A0, A1);
+#endif
+#if defined  (TARGET_DISCO_F746NG)  //  Computer is high-end ARM Cortex development system with touch lcd
+#include "BufferedSoftSerial.h"
+    BufferedSoftSerial gps_module   (A4, A5);
+#endif
+#ifdef  TARGET_NUCLEO_L432KC
+#include "BufferedSerial.h"
+    extern  BufferedSerial gps_module;
+#endif
+gps_mod::gps_mod()  //  Constructor
+{
+//BufferedSoftSerial gps_module   (_pinTx : A0, _pinRx : A1);
+    state = WAITING4DOLLAR;
+    destptr = dest;
+    strcpy  (latstr, "Invalid\0");
+    strcpy  (longstr, "Invalid\0");
+    strcpy  (altistr, "Invalid\0");
+    strcpy  (timestr, "??:??:??\0");
+    chcnt = 0;
+    inmsg = false;
+    newdata = false;
+    posfix[0] = 'x';
+    posfix[1] = 0;
+    return;
+}
+
+bool    gps_mod::new_data()  {
+    bool    r;
+    r = newdata;
+    newdata = false;
+    return  r;    
+}
+
+bool    gps_mod::fix_valid()
+{
+    if  (posfix[0] < '1' || posfix[0] > '3')    return  false;
+    return  true;
+}
+
+char * gps_mod::date ()  {
+    return  datestr;
+}
+
+char * gps_mod::heading ()   {
+    return  headingstr;
+}
+
+char * gps_mod::mph ()   {
+    return  speed_mphourstr;
+}
+
+char *     gps_mod::position_fix_indicator  ()
+{
+    return  posfix;
+}
+
+char * gps_mod::latitude()
+{
+    if  (fix_valid())    return  latstr;
+    return  "No Fix ";
+}
+
+char * gps_mod::longitude()
+{
+    if  (fix_valid())    return  longstr;
+    return  "No Fix ";
+}
+
+char * gps_mod::altitude()
+{
+    if  (fix_valid())    return  altistr;
+    return  "No Fix ";
+}
+
+char * gps_mod::sat_count ()
+{
+    return  numofsats;
+}
+
+char * gps_mod::message(int mess_num)
+{
+    if  (mess_num < 0 || mess_num > 5)
+        return  NULL;
+    return  dest + mess_num * (MAXMSGLEN + 4);
+}
+
+char * gps_mod::time ()
+{
+//    char * t = message(0);
+    return  timestr;
+}
+
+double gps_mod::lat_merged ()
+{
+    double  result = strtod (latstr, NULL);
+    return  result + (strtod (latstr + 3, NULL) / 60.0);
+}
+
+double gps_mod::lon_merged ()
+{
+    double  result = strtod (longstr, NULL);
+    return  result + (strtod (longstr + 4, NULL) / 60.0);
+}
+
+int gps_mod::update()
+{
+    static const char month_tab[] = "---\0Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec\0";
+    const int MAX_COMMAS_IN_MSG = 24;
+    int date, month;
+    while   (gps_module.readable())  {
+        ch = gps_module.getc();
+        switch  (state) {
+            case    WAITING4DOLLAR:
+                i = 0;
+                if  (ch == '$') state = GOTDOLLAR;
+                break;
+            case    GOTDOLLAR:  //  accumulate 5 chars after '$'
+                hdracc[i++] = ch;
+                hdracc[i] = 0;
+                if  (i > 4) {
+                    i = -1;
+                    state = GOTHDR;
+                }
+                break;
+            case    GOTHDR: //  have read "$GP???" to get here
+//                if  (ch != ',') pc.printf   ("Oops, comma missing\r\n");
+                if      (strncmp(hdracc,"GPGGA",5) == 0)  i = 0;
+                else if (strncmp(hdracc,"GPGLL",5) == 0)  i = 1;
+                else if (strncmp(hdracc,"GPGSA",5) == 0)  i = 2;
+                else if (strncmp(hdracc,"GPGSV",5) == 0)  i = 3;
+                else if (strncmp(hdracc,"GPRMC",5) == 0)  i = 4;//161229.487,A,3723.24756, N,12158.34162,W,     0.13,       309.62,  120598,,*10
+                //                                                  time     ?    lat            lon      ground speed knots|heading|date
+                else if (strncmp(hdracc,"GPVTG",5) == 0)  i = 5;
+                if  (i < 0) state = WAITING4DOLLAR;     //  Found invalid header info
+                else    {                               //  Validated header info
+                    destptr = message(i);
+                    chcnt = 0;
+                    state = WAITING4CR;
+                }
+                break;
+            case    WAITING4CR:
+                if  (ch == '\r')  {
+                    state = WAITING4LF;     //  process new string data here
+                    int comma_count = 0, comma_positions[MAX_COMMAS_IN_MSG + 2];
+                    for (int j = 0; j < strlen(destptr) && j < MAXMSGLEN && comma_count < MAX_COMMAS_IN_MSG; j++)  {
+                        if  (destptr[j] == ',') {
+                            comma_positions[comma_count++] = j;
+                            comma_positions[comma_count] = 0;
+                        }
+                    }
+                    switch  (i) {
+                        case    0:  //  GGA 183655.00,5110.25620,N,00320.09926,W,1,11,0.80,59.9,M,49.5,M,,*74
+                            if  (comma_positions[0] == 9)    {   //  time data seems to exist
+                                timestr[0] = dest[0];
+                                timestr[1] = dest[1];
+                                timestr[2] = ':';
+                                timestr[3] = dest[2];
+                                timestr[4] = dest[3];
+                                timestr[5] = ':';
+                                timestr[6] = dest[4];
+                                timestr[7] = dest[5];
+                                timestr[8] = 0;
+                            } else    {
+                                strcpy  (timestr, "Invalid");
+                            }
+                            posfix[0] = dest[comma_positions[4] + 1];
+                            if  (fix_valid())   {
+                                int a = 0, b = comma_positions[5] + 1;   //  get numof sats
+                                if  (dest[b] == ',')    {
+                                    numofsats[0] = '-';
+                                    numofsats[1] = '-';
+                                }
+                                else    {
+                                    numofsats[0] = dest[b++];
+                                    numofsats[1] = dest[b++];
+                                }
+                                b = comma_positions[7] + 1;  //  get altitude
+                                char c;
+                                while   ((c = dest[b++]) != ',' && a < 7)
+                                    altistr[a++] = c;
+                                altistr[a] = 0;
+                            }
+                            numofsats[2] = 0;
+                            break;
+                        case    1:  //  GLL 5110.25606,N,00320.10001,W,183752.00,A,A*72
+                            latstr[0] = destptr[0];
+                            latstr[1] = destptr[1];
+                            latstr[2] = '~';
+                            latstr[3] = destptr[2];
+                            latstr[4] = destptr[3];
+                            latstr[5] = '.';
+                            for (int k = 5; k < 10; k++)
+                                latstr[k + 1] = destptr[k];
+                            latstr[11] = ' ';
+                            latstr[12] = destptr[11];   //  N or S
+                            latstr[13] = 0;
+                            for (int k = 0; k < 3; k++)
+                                longstr[k] = destptr[k + 13];
+                            longstr[3] = '~';
+                            longstr[4] = destptr[16];
+                            longstr[5] = destptr[17];
+                            longstr[6] = '.';
+                            for (int k = 7; k < 12; k++)
+                                longstr[k] = destptr[k + 12];
+                            longstr[12] = ' ';
+                            longstr[13] = destptr[25];  //  E or W
+                            longstr[14] = 0;
+                            break;
+                        case    2:  //  GSA A,3,11,22,01,03,17,23,19,14,09,,,,1.72,0.97,1.42*0E
+                            break;
+                        case    3:  //  GSV complex multiple
+                            break;  //               0             1              2 3      4            5        67
+                        case    4:  //  RMC 184111.00, A,5110.25663, N,00320.10076,W, 0.223,            ,  090816,,,A*6B
+//                                  //      161229.487,A,3723.24756, N,12158.34162,W, 0.13,       309.62,  120598,,*10
+//                                           time     ?    lat            lon  ground speed knots|heading|date
+                            if  (comma_positions[6] + 1 == comma_positions[7])  //  no heading data exists
+                                strcpy  (headingstr, "-/-");
+                            else
+                                sprintf(headingstr, "%.1f", strtod(destptr + comma_positions[6] + 1, NULL));
+//                            pc.printf("%s ^%s^", destptr, headingstr);
+                            date =  (destptr[comma_positions[7] + 1] - '0') * 10 + (destptr[comma_positions[7] + 2] - '0');
+                            month = (destptr[comma_positions[7] + 3] - '0') * 10 + (destptr[comma_positions[7] + 4] - '0');
+                            if  (month > 12 || month < 1)
+                                month = 0;
+                            month *= 4;
+                            sprintf (datestr, "%d %s 20%d", date, &month_tab[month], strtol(&destptr[comma_positions[7] + 5], NULL, 10));
+                            break;
+                        case    5:  //  VTG       ,T   ,             ,M,0.098,N,0.181,K,A*2A course over ground and ground speed
+//$                                   GPVTG 309.62,T   ,             ,M,0.13, N,0.2,  K*6E
+                            sprintf (speed_mphourstr, "%.1fmph", km2miles * strtod(destptr + comma_positions[5] + 1, NULL));
+                            newdata = true;
+                            break;  //      course true|course magnetic|knots|km/hour
+                        default:
+//                            pc.printf   ("Naf i = %d\r\n", i);
+                            break;
+                    }
+                } else    { //  char is not CR
+                    if  (chcnt > MAXMSGLEN)    chcnt = MAXMSGLEN;
+                    destptr[chcnt++] = ch;
+                    destptr[chcnt] = 0;
+                }
+                break;
+            case    WAITING4LF:
+//                if  (ch != '\n')
+//                    pc.printf   ("Oops, no LF\r\n");
+                state = WAITING4DOLLAR;
+                break;
+            default:
+                break;
+        }
+    }
+    return 0;
+}   //      end of int gps_mod::update()
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gps_mod.h	Mon Jul 27 08:44:59 2020 +0000
@@ -0,0 +1,58 @@
+#ifndef MBED_GPS_MOD_H
+#define MBED_GPS_MOD_H
+ 
+#include "mbed.h"
+const static double PI = 2.0 * acos(0.0),
+                    deg2rad = (PI / 180.0),
+                    km2miles = (39370.0/36.0/1760.0);
+class gps_mod
+{
+    enum    {NULLST, WAITING4DOLLAR, GOTDOLLAR, GOTHDR, WAITING4CR, WAITING4LF};
+    const   static  int MAXMSGLEN =  86;
+//    private: by default
+    bool    inmsg, newdata;
+    int ch, state, i, chcnt;
+    char hdracc[8], dest[6*(MAXMSGLEN + 4)],
+         posfix[4],
+         numofsats[4],
+         latstr[16],
+         longstr[16],
+         altistr[16],
+         speed_mphourstr[16],
+         headingstr[16],
+         datestr[16],
+         timestr[16];
+    char * destptr;
+public:
+//BufferedSoftSerial gps_module   (PinName _pinTx, PinName _pinRx);
+    gps_mod();
+    int update();
+    void   tock_handler();
+    char * position_fix_indicator  ();
+    char * message(int);
+    char * time ();
+    char * date ();
+    char * heading ();
+    char * mph ();
+    char * sat_count ();
+    char * latitude();
+    char * longitude();
+    char * altitude();
+    bool    fix_valid();
+    bool    new_data();
+    double mergedegmin (char *, char *);
+    double lat_merged ();
+    double lon_merged ();
+};
+ 
+//class Flasher {
+//public:
+//    Flasher(PinName pin);
+//    void flash(int n);
+//  
+//private:  
+//    DigitalOut _pin;
+//};
+ 
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/i2c_2020_06.cpp	Mon Jul 27 08:44:59 2020 +0000
@@ -0,0 +1,112 @@
+#include "mbed.h"
+#include "Alternator.h"
+#include "I2CEeprom.h"
+
+const   int settings_ee_addr    = eeprom_page * eeprom_page_size;
+
+/*struct  sldandt  {
+    const uint32_t     min, max, de_fault;  //  min, max, default
+    const char * txt;     //  description
+    char    val;
+}   ;
+*/
+const char tabletext[] = "User Table entry 0 to 100";
+extern  const int numof_op_modes;
+
+struct  sldandt option_list[] = {
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 0
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 1
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 2
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 3
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 4
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 5
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 6
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 7
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 8
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 9
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 10
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 11
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 12
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 13
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 14
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 15
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 16
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 17
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 18
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 19
+    {0, 100, 0,     tabletext},       //  Field Current Limit User Entry Table position 20
+    {0, 120, 3,     "Engine warn-up delay seconds"},
+    {0, 100, 20,    "Warmup servo position percent"},
+    {0, numof_op_modes - 1, 0,     "Operating Mode - 0=Engine Only test, 1=Const Voltage, 2=Var Voltage, 3=Current feedback"},
+    {20, 100, 50,   "Engine speed control 'P' adjust"},
+    {0, 1, 0,       "0 Servo normal, 1 reverse"},
+    {0, 100, 0,     "Future 26"},
+    {0, 100, 0,     "Future 27"},
+    {0, 100, 0,     "Future 28"},
+    {0, 100, 0,     "Future 29"},
+    {0, 100, 0,     "Future 30"},
+    {0, 100, 0,     "Future 31"},
+}   ;
+
+/*enum    {TABL0, TABL1, TABL2, TABL3, TABL4, TABL5, TABL6, TABL7, TABL8, TABL9, TABL10, TABL11, TABL12, TABL13, TABL14, TABL15, TABL16, 
+        TABL17, TABL18, TABL19, TABL20,   
+        WARM_UP_DELAY, WARMUP_SERVO_POS, OP_MODE, SPEED_CTRL_P, SERVO_DIR, FUT30, FUT31 }  ;
+*/
+const   int    numof_eeprom_options    = sizeof(option_list) / sizeof (struct sldandt);
+
+
+//I2CEeprom eeprom  (SDA_PIN, SCL_PIN, 0xa0, eeprom_page_size, 8192, 100000);
+extern  I2CEeprom eeprom;   //  (SDA_PIN, SCL_PIN, 0xa0, eeprom_page_size, 8192, 100000);
+
+/*
+class   ee_settings_2020    {
+    char    new_settings[eeprom_page_size + 2];
+public:
+    ee_settings_2020    ()  ;   //  Constructor
+    int load    ()  ;
+    int save    ()  ;
+    char    rd    (uint32_t   i)  ;
+    bool    wr   (char c, uint32_t i)  ;           //  Write one setup char value to private buffer 'settings'
+}   ;
+*/
+    ee_settings_2020::ee_settings_2020  ()  {
+//        load    ();   //  Can't use this here, causes core dump
+    }  
+    
+int ee_settings_2020::load    ()  {
+    return  eeprom.read (settings_ee_addr, new_settings, eeprom_page_size);
+}
+
+int ee_settings_2020::save    ()  {   //  Write to eeprom only those that need changing
+    int count = 0;
+    char    tmp[eeprom_page_size + 2];
+    int n = eeprom.read (settings_ee_addr, tmp, eeprom_page_size);
+    for (int i = 0; i < eeprom_page_size; i++)  {
+        if  (new_settings[i] != tmp[i]) {
+            count++;
+            eeprom.write    (settings_ee_addr + i, new_settings[i]);
+        }
+    }
+    return  count;
+}
+
+char    ee_settings_2020::rd    (uint32_t   i)  {
+    return  new_settings[i];
+}
+
+
+bool    ee_settings_2020::wr   (char c, uint32_t i)  {           //  Write one setup char value to private buffer 'settings'
+    if  (i >= eeprom_page_size)
+        return  false;
+    new_settings[i] = c;
+    return  true;
+}
+
+sldandt *    ee_settings_2020::inform (uint32_t which)  {
+    if  (which >= numof_eeprom_options) 
+        return  NULL;
+    return &option_list[which];
+}
+
+
+
--- a/i2c_bit_banged.cpp	Mon Jun 08 13:46:52 2020 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,466 +0,0 @@
-#include "mbed.h"
-#include "Alternator.h"
-
-#define I2CTEST
-
-#ifdef  TARGET_NUCLEO_L432KC    //
-#ifdef  I2CTEST
-extern  Serial  pc;
-extern  I2C i2c;
-const int ACK     = 1;  //  but acknowledge is 0, NAK is 1 6/6/2020 think this should be 1
-
-#else
-
-extern  Serial  pc;
-DigitalInOut    SDA (D4);       //  Horrible bodge to get i2c working using bit banging.
-DigitalInOut    SCL (D5);       //  DigitalInOut do not work as you might expect. Fine if used only as OpenDrain opuputs though!
-DigitalIn       SDA_IN  (A4);   //  That means paralleling up with two other pins as inputs
-DigitalIn       SCL_IN  (A5);   //  This works but is a pain. Inbuilt I2C should have worked but never does on small boards with 32 pin cpu.
-const int ACK     = 0;  //  but acknowledge is 0, NAK is 1
-#endif
-#ifdef  TARGET_NUCLEO_F401RE    //
-extern  BufferedSerial  pc;
-extern  I2C i2c;
-const int ACK     = 1;  //  but acknowledge is 0, NAK is 1
-#endif
-#endif
-const int _24LC_rd = 0xa1;  //  set bit 0 for read, clear bit 0 for write
-const int _24LC_wr = 0xa0;  //  set bit 0 for read, clear bit 0 for write
-
-
-/*struct  optpar  {
-    int min, max, def;  //  min, max, default
-    const char * t;     //  description
-}   ;*/
-struct  optpar option_list2[] = {
-    {0, 100, 1, "max pwm% @ Eng RPM 0, 0 to 100"},
-    {0, 100, 1, "max pwm% @ Eng RPM 500, 0 to 100"},
-    {0, 100, 1, "max pwm% @ Eng RPM 1000, 0 to 100"},
-    {0, 100, 1, "max pwm% @ Eng RPM 1500, 0 to 100"},
-    {0, 100, 1, "max pwm% @ Eng RPM 2000, 0 to 100"},
-    {0, 100, 10, "max pwm% @ Eng RPM 2500, 0 to 100"},
-    {0, 100, 60, "max pwm% @ Eng RPM 3000, 0 to 100"},
-    {0, 100, 70, "max pwm% @ Eng RPM 3500, 0 to 100"},
-    {0, 100, 60, "max pwm% @ Eng RPM 4000, 0 to 100"},
-    {0, 100, 50, "max pwm% @ Eng RPM 4500, 0 to 100"},
-    {0, 100, 40, "max pwm% @ Eng RPM 5000, 0 to 100"},
-    {0, 100, 33, "max pwm% @ Eng RPM 5500, 0 to 100"},
-    {0, 100, 30, "max pwm% @ Eng RPM 6000, 0 to 100"},
-    {0, 100, 30, "max pwm% @ Eng RPM 6500, 0 to 100"},
-    {0, 100, 40, "max pwm% @ Eng RPM 7000, 0 to 100"},
-    {0, 100, 50, "max pwm% @ Eng RPM 7500, 0 to 100"},
-    {0, 100, 60, "max pwm% @ Eng RPM 8000, 0 to 100"},
-    {0, 100, 0, "Future 2"},
-    {0, 100, 0, "Future 3"},
-    {0, 100, 0, "Future 4"},
-    {0, 100, 0, "Future 5"},
-    {0, 100, 0, "Future 6"},
-    {0, 100, 0, "Future 7"},
-    {0, 100, 0, "Future 8"},
-    {0, 100, 0, "Future 9"},
-    {0, 100, 0, "Future 10"},
-    {0, 100, 0, "Future 11"},
-    {0, 100, 0, "Future 12"},
-    {0, 100, 0, "Future 13"},
-}   ;
-
-const   int    numof_eeprom_options2    = sizeof(option_list2) / sizeof (struct optpar);
-
-bool    wr_24LC64  (int start_addr, char * source, int length)   ;  //  think this works
-bool    rd_24LC64  (int start_addr, char * source, int length)   ;  //  think this works
-
-
-
-eeprom_settings user_settings  ;
-
-eeprom_settings::eeprom_settings    ()  {}
-
-bool    eeprom_settings::set_defaults   ()  {
-    for (int i = 0; i < numof_eeprom_options2; i++)
-        settings[i] = option_list2[i].def;       //  Load defaults and 'Save Settings'
-    return  save    ();
-}
-
-char    eeprom_settings::rd  (uint32_t i)  {           //  Read one setup char value from private buffer 'settings'
-    if  (i > 31)    {
-        pc.printf   ("ERROR Attempt to read setting %d\r\n", i);
-        return  0;
-    }
-    return  settings[i];
-}
-
-bool    eeprom_settings::rd  (char * c, uint32_t i)  {           //  Read one setup char value from private buffer 'settings'
-    if  (i > 31)    {
-        pc.printf   ("ERROR Attempt to read setting %d\r\n", i);
-        return  false;
-    }
-    *c = settings[i];
-    return  true;
-}
-
-bool    eeprom_settings::wr  (char c, uint32_t i)  {           //  Write one setup char value to private buffer 'settings'
-    if  (i > 31)
-        return  false;
-    settings[i] = c;
-    return  true;
-}
-
-/*double  eeprom_settings::get_pwm (int rpm)   {
-    int p = rpm * lut_size;
-    p /= 8000;  //  8000 is upper RPM limit, p now scaled to sizeof lut
-    if  (p < 0) p = 0;                    //  point to first
-    if  (p >= lut_size) p = lut_size - 1; //  point to last
-//    pc.printf   ("In get_pwm, rpm = %d, lut entry = %d, pwm = %d\r\n", rpm, p, max_pwm_lut[p]);
-    return  max_pwm_lut[p];
-}*/
-
-/*void    eeprom_settings::build_lut   ()  {
-    int ptr = 0;
-    int range, i;
-    double  acc = 0.0, incr = 0.0;
-    for (i = 0; i < 8; i++) {
-        range = user_settings.rd(i+1) - user_settings.rd(i);  //  range now change in percent between two 'n'000 RPMs
-        incr = (double)range;
-        incr /= 100.0;  //  percent
-        incr /= lut_seg_size;
-        for(int j = 0; j < lut_seg_size; j++)   {
-            max_pwm_lut[ptr++] = acc;
-            acc += incr;
-        }
-    }
-    max_pwm_lut[ptr] = (int)acc;
-    pc.printf   ("At end of build_lut ptr=%d\r\n", ptr);
-    range = 0;
-
-    while   (range < ptr)   {
-        for (i = 0; i < 8; i++)    {
-            pc.printf   ("%.3f\t", max_pwm_lut[range++]);
-        }
-        pc.printf   ("\r\n");
-    }
-    pc.printf   ("lut_size = %d\r\n", lut_size);
-
-}*/
-
-bool    eeprom_settings::load    ()  {               //  Get 'settings' buffer from EEPROM
-    bool    rv  ;
-    rv =    rd_24LC64   (eeprom_page * 32, settings, 32);   //  Can now build lookup table
-//Apr2020    build_lut   ();
-    return  rv;
-}
-
-bool    eeprom_settings::save    ()  {               //  Write 'settings' buffer to EEPROM
-    return  wr_24LC64   (eeprom_page * 32, settings, 32);
-}
-
-
-//#ifdef  TARGET_NUCLEO_L432KC    //
-
-/**
-*   bool    i2c_init(void) {
-*
-*   Init function. Needs to be called once in the beginning.
-*   Returns false if SDA or SCL are low, which probably means 
-*   a I2C bus lockup or that the lines are not pulled up.
-*/
-bool    i2c_init(void) {
-#ifdef  TARGET_NUCLEO_L432KC    //
-#ifndef I2CTEST
-    SDA.output();
-    SCL.output();
-    SDA.mode(OpenDrain);
-    SCL.mode(OpenDrain);    //  Device may pull clock lo to indicate to master
-    SDA = 0;
-    SCL = 0;
-    wait_us   (1);
-    SDA = 1;
-    wait_us (1);
-    SCL = 1;
-    wait_us (1);
-    if  (SCL_IN == 0 || SDA_IN == 0)    return  false;
-#endif
-#endif
-#ifdef  TARGET_NUCLEO_F401RE    //
-//    return  i2c.init    ()  ; //  class has no member "init"
-#endif
-    return  true;
-}
-
-/**
-*   During data transfer, the data line must remain 
-*   stable whenever the clock line is high. Changes in 
-*   the data line while the clock line is high will be 
-*   interpreted as a Start or Stop condition
-*
-*   A high-to-low transition of the SDA line while the clock
-*   (SCL)   is   high   determines   a   Start   condition.   All
-*   commands must be preceded by a Start condition.
-*/
-int i2c_start  ()  {   // Should be Both hi, start takes SDA low
-#ifdef  TARGET_NUCLEO_L432KC    //
-#ifndef I2CTEST
-    int    rv = 0;
-    if  (SDA_IN == 0 )  {
-        rv |= 1;    //  Fault - SDA was lo on entry
-        SDA = 1;
-        wait_us (1);
-    }
-    if  (SCL == 0 )  {
-        rv |= 2;    //  Fault - SCL was lo on entry
-        SCL = 1;
-        wait_us (1);
-    }
-    SDA = 0;                    //  Take SDA lo
-    wait_us (1);
-    SCL = 0;
-    wait_us (1);
-    return  rv;     //  Returns 0 on success, 1 with SDA fault, 2 with SCL fault, 3 with SDA and SCL fault
-#else
-    i2c.start   ()  ;
-    return  0;
-
-#endif
-#endif
-#ifdef  TARGET_NUCLEO_F401RE    //
-    i2c.start   ()  ;
-    return  0;
-#endif
-}
-
-/**
-*   During data transfer, the data line must remain 
-*   stable whenever the clock line is high. Changes in 
-*   the data line while the clock line is high will be 
-*   interpreted as a Start or Stop condition
-*
-*   A low-to-high transition of the SDA line while the clock
-*   (SCL)   is   high   determines   a   Stop   condition.   All
-*   operations must be ended with a Stop condition.
-*/
-int i2c_stop  ()  {   //  Should be SDA=0, SCL=1, start takes SDA hi
-#ifdef  TARGET_NUCLEO_L432KC    //
-#ifdef  I2CTEST
-    i2c.stop    ()  ;
-    return  0;
-#else
-    int    rv = 0;
-    SDA = 0;    //  Pull SDA to 0
-    wait_us (1);
-    if  (SCL_IN != 0)   {
-        pc.printf   ("SCL 1 on entry to stop\r\n");
-        SCL = 0;    //  pull SCL to 0 if not there already
-        wait_us (1);
-    }
-    SCL = 1;
-    wait_us (1);
-    if  (SCL_IN == 0)
-        pc.printf   ("SCL stuck lo in stop\r\n");
-    SDA = 1;
-    wait_us (1);
-    if  (SDA_IN == 0)
-        pc.printf   ("SDA stuck lo in stop\r\n");
-    return  rv;     //  Returns 0 on success, 1 with SDA fault, 2 with SCL fault, 3 with SDA and SCL fault
-#endif
-#endif
-#ifdef  TARGET_NUCLEO_F401RE    //
-    i2c.stop    ()  ;
-    return  0;
-#endif
-}
-
-#ifdef  TARGET_NUCLEO_L432KC    //
-#ifndef I2CTEST
-void    jclk    (int bit)   {
-    SCL = bit;
-    wait_us (1);
-}
-
-void    jclkout ()  {
-    wait_us (1);
-    SCL = 1;
-    wait_us (1);
-    SCL = 0;
-    wait_us (1);
-}
-#endif
-#endif
-
-int i2c_write (int    d)  {
-#ifdef  TARGET_NUCLEO_L432KC    //
-#ifdef  I2CTEST
-    return  i2c.write   (d);
-#else
-    int ackbit = 0;
-    if  (SCL_IN != 0)   {
-        pc.printf   ("SCL hi on entry to write\r\n");
-        jclk    (0);
-    }
-    for (int i = 0x80; i != 0; i >>= 1) {        //  bit out msb first
-        if  ((d & i) == 0)  SDA = 0;
-        else                SDA = 1;
-        jclkout ();     //  SCL ____---____
-    }
-    SDA = 1;    //  Release data to allow remote device to pull lo for ACK or not
-    jclk    (1);    //  SCL = 1
-    ackbit = SDA_IN;   //  read in ack bit
-    jclk    (0);    //  SCL = 0
-//    pc.printf   ("wr 0x%x %s\r\n", d, ackbit == 0 ? "ACK" : "nak");
-    return  ackbit; //      0 for acknowledged ACK, 1 for NAK
-#endif
-#endif
-#ifdef  TARGET_NUCLEO_F401RE    //
-    return  i2c.write   (d);
-#endif
-}
-
-
-
-
-int i2c_read    (int acknak)  {     //  acknak  indicates if the byte is to be acknowledged (0 = acknowledge)
-#ifdef  TARGET_NUCLEO_L432KC    //
-#ifdef  I2CTEST
-    return  i2c.read    (acknak)    ;
-#else
-    int result = 0;                 //  SCL should be 1 on entry
-    SDA = 1;        //  Master released SDA
-    if  (SCL_IN != 0)   pc.printf   ("SCL hi arriving at read\r\n");
-    wait_us (2);
-    for (int i = 0; i < 8; i++) {
-        result <<= 1;
-        jclk    (1);
-        if  (SDA_IN != 0)  result |= 1;
-        jclk    (0);
-    }
-    if  (acknak != 0 && acknak != 1)
-        pc.printf   ("Bad acknak in 12c_read %d\r\n", acknak);
-    if  (acknak == 0)   SDA = 0;
-    else                SDA = 1;
-    jclkout ();    //  clock out the ACK bit __--__
-//    pc.printf   ("rd 0x%x %s\r\n", result, acknak == 0 ? "ACK" : "nak");
-    return  result;             //  Always ? nah
-#endif
-#endif
-#ifdef  TARGET_NUCLEO_F401RE    //
-    return  i2c.read    (acknak)    ;
-#endif
-}
-
-int check_24LC64   ()  {     //  Call from near top of main() to init i2c bus
-    int last_found = 0, q, e;      //  Note address bits 3-1 to match addr pins on device
-    for (int i = 0; i < 255; i += 2)    {   //  Search for devices at all possible i2c addresses
-        e = i2c_start();
-        if  (e) pc.putc(',');
-        q = i2c_write(i);   //  may return error code 2 when no start issued
-        if  (q == ACK)  {
-            pc.printf   ("I2C device found at 0x%x\r\n", i);
-            last_found = i;
-            wait_ms (5);
-        }
-        i2c_stop();
-    }
-    return  last_found;
-}
-
-
-
-
-
-
-
-
-bool    ack_poll    ()  {   //  wait short while for any previous memory operation to complete
-    const int poll_tries    = 40;
-    int poll_count = 0;
-    bool    i2cfree = false;
-    while   (poll_count++ < poll_tries && !i2cfree)  {
-        i2c_start   ();
-        if  (i2c_write(_24LC_wr) == ACK)
-            i2cfree = true;
-        else
-            wait_ms   (1);
-    }
-//    pc.printf   ("ack_poll, count = %d, i2cfree = %s\r\n", poll_count, i2cfree ? "true" : "false");
-    return  i2cfree;
-}
-
-/**bool    set_24LC64_internal_address (int    start_addr)   {
-*
-*
-*
-*/
-bool    set_24LC64_internal_address (int    start_addr)   {
-    if  (!ack_poll())
-    {
-        pc.printf   ("Err in set_24LC64_internal_address, no ACK writing device address byte\r\n");
-        i2c_stop();
-        return  false;
-    }
-    int err = 0;
-    if  (i2c_write(start_addr >> 8)   != ACK) err++;
-    if  (i2c_write(start_addr & 0xff) != ACK) err++;
-    if  (err)   {
-        pc.printf   ("In set_24LC64_internal_address, Believe Device present, failed in writing 2 mem addr bytes %d\r\n", err);
-        i2c_stop();
-        return  false;
-    }
-//    pc.printf   ("GOOD set_24LC64_internal_address %d\r\n", start_addr);
-    return  true;
-}
-
-bool    wr_24LC64  (int start_addr, char * source, int length)   {  //  think this works
-    int err = 0;
-    if(length < 1 || length > 32)   {
-        pc.printf   ("Length out of range %d in wr_24LC64\r\n", length);
-        return  false;
-    }
-    ack_poll    ();
-    if  (!set_24LC64_internal_address   (start_addr))   {
-        pc.printf   ("In wr_24LC64, Believe Device present, failed in writing 2 mem addr bytes %d\r\n", err);
-        return  false;
-    }
-    while(length--) 
-        if  (i2c_write(*source++) != ACK)
-            err++;
-//        err += i2c_write(*source++);
-    i2c_stop();
-    if  (err)   {
-        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");
-    return  true;
-}
-
-bool rd_24LC64  (int start_addr, char * dest, int length)   {
-    int acknak = ACK;
-    if(length < 1)
-        return false;
-    if  (!set_24LC64_internal_address   (start_addr))   {
-        pc.printf   ("In rd_24LC64, failed to set_ramaddr\r\n");
-        return  false;
-    }
-    i2c_start();
-    if  (i2c_write(_24LC_rd) != ACK) {
-        pc.printf   ("Errors in rd_24LC64 sending 24LC_rd\r\n");
-        return  false;
-    }
-    while(length--) {
-        if(length == 0)
-            acknak = 1;
-        *dest++ = i2c_read(acknak);
-    }
-    i2c_stop();
-    return  true;
-}
-
-
-
-
-
-
-
-
-
-
-
-
--- a/main.cpp	Mon Jun 08 13:46:52 2020 +0000
+++ b/main.cpp	Mon Jul 27 08:44:59 2020 +0000
@@ -1,31 +1,48 @@
 #include "mbed.h"
 #include "Alternator.h"
-/*
-Test 6th June 2020 - i2c sda=grey, scl=white
-*/
-float  dpd = 0.0;
+#include "BufferedSerial.h"
+#include "I2CEeprom.h"
+#include "LM75B.h"              //  New I2C temp sensor code March 2020 (to suit possible next board issue, harmless otherwise)
+#include "rpm.h"
+#include "field.h"
+//#include "gps_mod.h"
+//#include "baro.h"
+
+#ifdef  TARGET_NUCLEO_L432KC    //  24LC and LM75 work
+//    #define SDA_PIN D0    //  good
+//    #define SCL_PIN D1
+    #define SDA_PIN D4      //  good
+    #define SCL_PIN D5
+#endif
+
+/*******************************************************************************
+        DON'T FORGET TO REMOVE SOLDER LINKS SB16 AND SB18 ON L432KC BOARD
+*******************************************************************************/
+//During test LED across field has been useful visual aid. Incorporate similar on new board.
 /*
     *   May 2020 NOTE input circuit to analogue in driver pot zeners input to 3v6, then pot reduces by about 1/3.
     *   This makes input reading only about 0.0 to 0.66
     *   Temp bodge, mult by 1.5
+    
+    *   Two voltages now measured. Link voltage (alternator output), and field supply (which may come from battery when main output low)
 */
 
 /*
     Alternator Regulator
     Jon Freeman
-    June 2019 - Feb 2020
+    June 2019 - June 2020
     
-    ** Prototype built using Nucleo L432KC. Final design likely to use F401RE. Code should compile for either. **
+    ** Prototype built using Nucleo L432KC. Having solved i2c problem, this looks good for final product
     
     **  main loop frequency upped from 32Hz to 100Hz **
 
     WHAT THIS PROGRAMME DOES - Controls 4 stroke petrol engine driving vehicle alternator with new custom regulator
     
-    Electronics powered by higher voltage of small 12v backup battery, or alternator field output supply
+    Electronics powered by higher voltage of small 12v backup battery, or alternator output
         Note only Field+ and MAX5035 supplied thus, all else powered from MAX outputs.
     Starting engine provides rectified tickle from magneto to enable MAX5035 creating +5 and +3v3 supplies.
-    Alternative, selected by jumper pposition, is external switch - battery+ to MAX enable circuit.
-    Anytime engine revs measured < TICKOVER_RPM (or some such) RPM, field current OFF (by pwm 0)
+    Alternative, selected by jumper position, is external switch - battery+ to MAX enable circuit. ** Review this **
+    Anytime engine revs measured < TICKOVER_RPM (or some such) RPM, field current OFF (by pwm 0) , see speed related pwm limit table
     
     BEGIN
         Loop forever at 100 Hz   {
@@ -46,42 +63,31 @@
     OUTPUT  pwm to MCP1630. This is clock to pwm chip. Also limits max duty ratio
     RS232 serial via USB to setup eeprom data
 */
-//  Uses software bit banged I2C - DONE (because no attempt to get I2C working on these small boards has ever worked)
 
 /**
 *   Jumpers fitted to small mbed Nucleo boards - D5 - A5 and D4 - A4 CHECK - yes
 */
-//#ifdef  TARGET_NUCLEO_F303K8    //  Code too large to fit
-#ifdef  TARGET_NUCLEO_L432KC    //
-/*
-    declared in file i2c_bit_banged.cpp
-DigitalInOut    SDA (D4);       //  Horrible bodge to get i2c working using bit banging.
-DigitalInOut    SCL (D5);       //  DigitalInOut do not work as you might expect. Fine if used only as OpenDrain opuputs though!
-DigitalIn       SDA_IN  (A4);   //  That means paralleling up with two other pins as inputs
-DigitalIn       SCL_IN  (A5);   //  This works but is a pain. Inbuilt I2C should have worked but never does on small boards with 32 pin cpu.
-*/
-Serial  pc      (USBTX, USBRX); //  Comms port to pc or terminal using USB lead
-
-
+//#ifdef  TARGET_NUCLEO_L432KC    //  Has to be, quite settled now on this, having solved i2c problems
+BufferedSerial  pc      (USBTX, USBRX, 4096, 4, NULL); //  Comms port to pc or terminal using USB lead
 //BufferedSerial  LocalCom    (PA_9, PA_10);  //  New March 2019 - Taken out for i2c test 6/6/2020
 
-
-//  Above combo of Serial and BufferedSerial is the only one to work !
-
 //  INPUTS :
-AnalogIn    Ain_SystemVolts (A6);   //  Sniff of alternator output, not used in control loop as done using analogue MCP1630
-//AnalogIn    Ammeter_In      (A1);   //  Output of ASC709LLFTR ammeter chip (pin 20), used to increase engine revs if need be
+AnalogIn    Ain_Link_Volts  (A6);   //  Sniff of alternator output, not used in control loop as done using analogue MCP1630
+AnalogIn    Ammeter_In      (A1);   //  Output of ASC709LLFTR ammeter chip (pin 20), used to increase engine revs if need be
 //AnalogIn    Ammeter_Ref     (A0);   //  Ref output from ASC709LLFTR used to set ammeter zero (pin 25)
 
-//  Nov 2019. Not convinced Ext_Rev_Demand is useful
+//  Nov 2019. Not convinced Ext_Rev_Demand is useful    ** July 2020 - repurposed, voltmeter Field_Supply_V
 //AnalogIn    Ext_Rev_Demand  (D3);   //  Servo determines engine revs, servo out to be higher of Ext_Rev_Demand and internal calc
+AnalogIn    Field_Supply_V  (D3);   //  Servo determines engine revs, servo out to be higher of Ext_Rev_Demand and internal calc
 
 AnalogIn    Driver_Pot      (A3);   //  If whole control system can be made to fit
 
+BufferedSerial  gps_module  (D1, D0, 2048, 4, NULL);   //  For gps - added July 2020
+
 /*
     MODULE PIN USAGE    
-1   PA_9 D1     LocalCom Tx
-2   PA_10 D0    LocalCom Rx
+1   PA_9 D1     LocalCom Tx FUTURE i2c to enable 3rd independent pwm
+2   PA_10 D0    LocalCom Rx FUTURE i2c to enable 3rd independent pwm
 3   NRST        
 4   GND     
 5   PA12_D2     NEW June 2019 - Output engine tacho cleaned-up, brought out to testpoint 4
@@ -92,7 +98,7 @@
 10  N.C.        
 11  N.C.        
 12  PA_8 D9     InterruptIn pulse_tacho from engine magneto, used to measure rpm
-13  PA_11 D10   Throttle servo
+13  PA_11 D10   Speed_Control servo
 14  PB_5 D11   //  InterruptIn VEXT PWM controller output folded back for cpu to monitor, useful on test to read what pwm required to do what
 15  PB_4 D12    Scope_probe
 16  PB_3 D13 LED    Onboard LED
@@ -113,120 +119,96 @@
 */
 
 //  Test 6/6/2020 to get i2c working
-//I2C i2c                     (D0, D1);   //  For 24LC64 eeprom
-//I2C i2c                     (D0, D1);   //  For 24LC64 eeprom
-
-I2C i2c                     (D0, D1);   //  For 24LC64 eeprom
+//I2C i2c                     (D0, D1);   //  For 24LC64 eeprom correct
 //I2C i2c                     (D1, D0);   //  For 24LC64 eeprom DEFINITELY WRONG
 //  Test 6/6/2020 to get i2c working
 
-
-InterruptIn pulse_tacho     (D9);  //  Signal from engine magneto (clipped by I limit resistor and 3v3 zener)
-InterruptIn VEXT            (D2);     //  PWM controller output folded back for cpu to monitor, useful on test to read what pwm required to do what
+//InterruptIn pulse_tacho     (D9);  //  Signal from engine magneto (clipped by I limit resistor and 3v3 zener)
+//                      Note D9 still used but taken to rpm class object
+//InterruptIn VEXT            (D2);     //  PWM controller output folded back for cpu to monitor, useful on test to read what pwm required to do what
+//                      Note D2 still used but taken to field class object
 //  OUTPUTS :
 
 DigitalOut  Scope_probe     (D12);   //  Handy pin to hang scope probe onto while developing code
 DigitalOut  myled           (LED1);        //  Green LED on board is PB_3 D13
-PwmOut      PWM_OSC_IN      (A2);   //  Can alter prescaler can not use A5
+//PwmOut      PWM_OSC_IN      (A2);   //  Can alter prescaler can not use A5    NOW DONE IN CLASS
 //PwmOut      A_OUT           (A2);   //  Can alter prescaler can not use A5    PIN STOLEN BY PWM_OSC_IN
-Servo       Throttle        (D10);   //  Changed from A2, June 2019
-DigitalOut  EngineTachoOut  (D11);   //  New June 2019
-#endif
-
-#ifdef  TARGET_NUCLEO_F401RE    //
-//Serial  pc      (USBTX, USBRX); //  Comms port to pc or terminal using USB lead
-BufferedSerial  pc          (PA_2, PA_3, 2048, 4, NULL);    //  Pins 16, 17    tx, rx to pc via usb lead
-//BufferedSerial  pc          (USBTX, USBRX);    //  Pins 16, 17    tx, rx to pc via usb lead
-BufferedSerial  LocalCom    (PC_6, PC_7);    //  Pins 37, 38  tx, rx to Touch Screen Controller
-
-//  INPUTS :
-AnalogIn    Ain_SystemVolts (PB_1);   //  Sniff of alternator output, not used in control loop as done using analogue MCP1630
-//AnalogIn    Ammeter_In      (PC_5);   //  Output of ASC709LLFTR ammeter chip (pin 20), used to increase engine revs if need be
-//AnalogIn    Ammeter_Ref     (PB_0);   //  Ref output from ASC709LLFTR used to set ammeter zero (pin 25)
-//AnalogIn    Ext_Rev_Demand  (PC_1);   //  Servo determines engine revs, servo out to be higher of Ext_Rev_Demand and internal calc
-AnalogIn    Driver_Pot      (PC_2);   //  If whole control system can be made to fit
 
-/*
-    MODULE PIN USAGE    
-*/
-
-InterruptIn pulse_tacho     (PB_15);  //  Signal from engine magneto (clipped by I limit resistor and 3v3 zener)
-InterruptIn VEXT            (PC_12);     //  PWM controller output folded back for cpu to monitor, useful on test to read what pwm required to do what
-//  OUTPUTS :
-
-DigitalOut  Scope_probe     (PB_3);   //  Handy pin to hang scope probe onto while developing code
-DigitalOut  myled           (PA_5);        //  Green LED on board is PA_5
-//PwmOut      PWM_OSC_IN      (PA_10);   //  PA_10 is pwm1/3 Can alter prescaler can not use A5
-PwmOut      PWM_OSC_IN      (PB_9);   //  PA_10 is pwm4/4 Can alter prescaler can not use A5
-PwmOut      A_OUT           (PB_5);   //  PB_5 is pwm3/2 Can alter prescaler can not use A5    PIN STOLEN BY PWM_OSC_IN
-Servo       Throttle        (PA_0);   //  PA_8 is pwm1/1 Changed from A2, June 2019
-DigitalOut  EngineTachoOut  (PA_7);   //  New June 2019
-
-I2C i2c                     (PB_7, PB_6);   //  Pins 58, 59 For 24LC64 eeprom
-//#define SDA_PIN PB_7
-//#define SCL_PIN PB_6
-
-#endif
-
+//PwmOut      Test_RPM    (PA_6);
 Timer   microsecs;      //  64 bit counter, rolls over in half million years
 Ticker  loop_timer;     //  Device to cause periodic interrupts, used to sync iterations of main programme loop - slow
 
-//const   double  AMPS_CAL = 90.0;
-extern  eeprom_settings user_settings  ;
+extern  char *  get_mode_text   (uint32_t mode)  ;
+
+
+I2CEeprom       eeprom  (SDA_PIN, SCL_PIN, 0xa0, eeprom_page_size, 8192, 100000);
+extern  ee_settings_2020 user_settings  ;
+Engine_Manager  Engine  (D9, D11, D10); //  Pins are magneto in, cleaned magneto out, servo, const debounce time microsecs
+FieldControl    Field   (A2, D2);       //  PWM pin for MCP1630, InterruptIn for signal out of MCP1630
+PCT2075         temp_sensor( SDA_PIN, SCL_PIN );    //  or LM75B temp_sensor( p?, p? );  Added March 2020
+
+//class   MPL3115A2   baro    ;
+
 //  SYSTEM CONSTANTS
 /*  Please Do Not Alter these */
 const   int     MAIN_LOOP_REPEAT_TIME_US    = 10000;    //  10000 us, with TACHO_TAB_SIZE = 100 means tacho_ticks_per_time is tacho_ticks_per_second
 /*  End of Please Do Not Alter these */
 /*  Global variable declarations */
-uint32_t
-            volt_reading        = 0,    //  Global updated by interrupt driven read of Battery Volts
-            driver_reading      = 0,
-//            tacho_count         = 0,    //  Global incremented on each transition of InterruptIn pulse_tacho
-            sys_timer100Hz      = 0;    //  gets incremented by our Ticker ISR every MAIN_LOOP_REPEAT_TIME_US
-double      servo_position = 0.2;   //  set in speed control loop
-double      throttle_limit = SERVO_MAX;
+uint32_t    sys_timer100Hz      = 0;    //  gets incremented by our Ticker ISR every MAIN_LOOP_REPEAT_TIME_US
+uint32_t    seconds = 0;
+double      link_volt_reading        = 0.0;    //  Global updated by interrupt driven read of Battery Volts at rate of 100 Hz
+double      field_volt_reading       = 0.0;    //  Global updated by interrupt driven read of Battery Volts at rate of 100 Hz
+double      amp_reading = 0.0;
 bool        loop_flag   = false;    //  made true in ISR_loop_timer, picked up and made false again in main programme loop
 bool        flag_25Hz   = false;    //  As loop_flag but repeats 25 times per sec
 bool        flag_12Hz5  = false;    //  As loop_flag but repeats 12.5 times per sec
 bool        flag_1Hz    = false;    //  As loop_flag but repeats 1 times per sec
 bool        query_toggle    = false;
 
-bool        flag_V_rd   = false;
+bool        flag_link_V_rd  = false;
+bool        flag_field_V_rd = false;
+bool        flag_A_rd   = false;
 bool        flag_Pot_rd = false;
-//const int AMP_FILTER_FACTOR    = 6;
 
+bool        auto_test_flag  = false;
+
+enum    {SAFE_NOTHING, POT_SERVO_DIRECT, VARIABLE_VOLTAGE, FIXED_VOLTAGE, ENG_REVS_CTRL, POT_SETS_ENGINE_RPM, CURRENT_FEEDBACK_CTRL, AUTO_TEST}  ;
 /*  End of Global variable declarations */
 
 //void    ISR_fast_interrupt  ()  {   //  here at 10 times main loop repeat rate (i.e. 1000Hz, 1.0ms)
 void    ISR_fast_interrupt  ()  {
-    static  uint32_t t = 0;
+    static  uint32_t t = 0, u25 = 0;
     Scope_probe = 1;    //  To show how much time spent in interrupt handler
     switch  (t) {
         case    0:
-            flag_V_rd = true;
-//            volt_reading    >>= 1;                                 //  Result = Result / 2
-//            volt_reading    += Ain_SystemVolts.read_u16    ();     //  Result = Result + New Reading
+            flag_link_V_rd = true;
             break;
-//        case    1:
-//            raw_amp_reading = (double) Ammeter_In.read();
-//            break;
+        case    1:
+            flag_A_rd = true;
+            break;
         case    2:
             flag_Pot_rd = true;
 //            raw_amp_offset = Ammeter_Ref.read();    //  Feb 2020 Not convinced this is useful
             break;
-//        case    3:
-//            ext_rev_req     >>= 1;                                 //  Result = Result / 2
-//            ext_rev_req     += Ext_Rev_Demand.read_u16();
-//            break;
-        case    4:
+        case    3:
+            flag_field_V_rd = true;
+            break;
+//        case    4:
 //            driver_reading  >>= 1;                                 //  Result = Result / 2
 //            driver_reading  += Driver_Pot.read_u16();
 //            break;
-//        case    5:
+        case    5:
             loop_flag = true;   //  set flag to allow main programme loop to proceed
             sys_timer100Hz++;        //  Just a handy measure of elapsed time for anything to use
-            if  ((sys_timer100Hz & 0x03) == 0)  //  is now 12.5Hz, not 8
+            if  ((sys_timer100Hz & 0x03) == 0)  {  //  is now 12.5Hz, not 8
                 flag_25Hz  = true; //  flag gets set 25 times per sec. Other code may clear flag and make use of this
+                u25++;
+                if  (u25 == 25) {
+                    u25 = 0;
+                    flag_1Hz = true;
+                    seconds++;
+                }
+            }
         default:
             break;
     }
@@ -236,361 +218,148 @@
     Scope_probe = 0;    //  To show how much time spent in interrupt handler
 }
 
-
-//  New stuff June 2019
-//  Decent way of measuring engine speed
-bool    magneto_stretch = false;
-Timeout magneto_timo;
-uint64_t magneto_times[4] = {13543,0,0,0};  //  June 2019, only 2 of these used. Big non-zero prevents div0 error on first pass
-
-
-/**    
-    void    magneto_timeout ()
-    Here 5ms after magneto pulse detected
-    This is sufficient time for ringing to cease, not long enough to lose next pulse even at max engine revs.
-    Reset 'magneto_stretch' flag set and used in 'ISR_magneto_tacho'
-*/
-void    magneto_timeout ()
-{
-    magneto_stretch = false;    //  Magneto ringing finished by now, re-enable magneto pulse count
-    EngineTachoOut  = 0;    //  Cleaned tacho output brought out to pin to look at with scope
-}
-
-/**
-void    ISR_magneto_tacho   ()  ;   //  New June 2019
-    //      Engine On/Off switch turns engine off by shorting ignition volts magneto to ground.
-    //      Therefore when engine running, have pulse signal one pulse per rev (even though 4 stroke, spark delivered at 2 stroke rate)
-    //      Pulse spacing 20ms @ 3000 RPM, 60ms @ 1000 RPM, 6ms @ 10000 RPM
-    
-            Magneto signal rings, is quite unclean, therefore a cleanup strategy is needed.
-            Solution - On arrival at this interrupt handler,
-                If flag 'magneto_stretch' true, do nothing and return (to avoid multiple pulse count)
-                Set flag 'magneto_stretch' true;
-                Start timer 'magneto_timo' to cause 'magneto_timeout' interrupt in a time longer than ringing bt shorter than shortest time to next spark
-                Record time between most recent two sparks and set output bit for scope monitoring
-*/
-void    ISR_magneto_tacho   ()  //  This interrupt initiated by rising (or falling) edge of magneto output, (not both)
-{
-    uint64_t    new_time;
-    if  (!magneto_stretch)      //  May get this interrupt more than once per magneto pulse, respond to first, lock out subsequent
-    {                           //  until magneto_timeout time has elapsed
-        magneto_stretch = true;
-        new_time = microsecs.read_high_resolution_us();
-        magneto_times[0] = new_time - magneto_times[1];    //  microsecs between most recent two sparks
-        magneto_times[1] = new_time;    //  actual time microsecs of most recent spark
-        magneto_timo.attach_us (&magneto_timeout, 5000);    //  To ignore ringing and multiple counts on magneto output, all settled within about 5ms
-        EngineTachoOut  = 1;    //  Cleaned tacho output brought out to pin to look at with scope
-    }
-}
-
-//  Endof New stuff June 2019
-
-
-    VEXT_Data   Field;
-
-
-void    ISR_VEXT_rise    ()  //  InterruptIn interrupt service
-{   //  Here is possible to read back how regulator has controlled pwm - may or may not be useful
-    uint64_t    tmp = microsecs.read_high_resolution_us();
-    Field.measured_period = tmp - Field.t_on;
-    Field.t_on = tmp;
-    Field.rise_count++;
-}
-void    ISR_VEXT_fall    ()  //  InterruptIn interrupt service
-{
-    Field.fall_count++;
-    Field.t_off = microsecs.read_high_resolution_us();
-    Field.measured_pw_us = Field.t_off - Field.t_on;
-}
 //  ****    End of Interrupt Service Routines   ****
 
-
-/** uint32_t    ReadEngineRPM  ()
-*   
-*   June 2019 - Replaced count of alternator frequency by count of engine magneto pulses.
-*   
-*/
-uint32_t    ReadEngineRPM  ()
-{
-    uint64_t time_since_last_spark = microsecs.read_high_resolution_us() - magneto_times[1];
-    if  (time_since_last_spark > 250000)     //  if engine probably stopped, return old method RPM
-        return  0;
-    return  (60000000 / magneto_times[0]);  //  60 million / microsecs between two most recent sparks, eg 10,000us between sparks @ 6000 RPM
-}
-
 /*double  Read_Ext_Rev_Req    ()
 {
     double  rv = (double) ext_rev_req;
     return  rv / 4096.0;
 }*/
-
+/*
 double  Read_Driver_Pot    ()
 {
     double  rv = (double) driver_reading;
     return  rv / 4096.0;
-}
-
-double  Read_BatteryVolts   ()
-{
-    return  ((double) volt_reading) / 3282.5;    //  divisor fiddled to make voltage reading correct !
-}
+}*/
 
-/**
-void    set_servo   (double p)  {   //  Only for test, called from cli
-*/
-void    set_servo   (double p)  {   //  Only for test, called from cli
-    Throttle = p;
-}
-
-double  normalise   (double * p)   {
-    if  (*p > 0.999)
-        *p = 0.999;
-    if  (*p < 0.001)
-        *p = 0.001;
-    return  * p;
+double  Read_Link_Volts   ()
+{
+    return  link_volt_reading * 39.9;    //  divisor fiddled to make voltage reading correct !
 }
 
-
-
-//const   double  DRIVER_NEUTRAL = 0.18;
-/**void    throttle_setter ()  {
-    *   
-    *   
-    *   
-    *   
-    *   
-    *   
-*/
-void    throttle_setter ()  {
-//    double  Driver_demand = Read_Driver_Pot();
-    const   double  local_hysterics = 0.03;
-    static  double  most_recent_throttle = 0.0;
-    double  Driver_demand = dpd;
-//    pc.printf   ("Pot\t%.2f  \r\n", Driver_demand);
-//    pc.printf   ("Pot\t%d\t%.3f  \r\n", driver_reading, dpd);     //  Shown pot drives servo over full range.
-    if  (Driver_demand < DRIVER_NEUTRAL)    {   //  In braking or park
-        Throttle = 0.0;
-    }
-    else    {   //  Driving
-        Driver_demand -= DRIVER_NEUTRAL;
-        Driver_demand /= (1.0 - DRIVER_NEUTRAL);    //  Re-normalise what's left
-        if  ((most_recent_throttle - Driver_demand < -local_hysterics) || (most_recent_throttle - Driver_demand > local_hysterics))  {
-            Throttle = Driver_demand;
-            most_recent_throttle = Driver_demand;
-            servo_position = Driver_demand;         //  Copy to global for pc.printf only May 2020
-        }
-    }
+double  Read_Field_Volts   ()
+{
+    return  field_volt_reading * 42.85;    //  divisor fiddled to make voltage reading correct !
 }
 
-/**void    set_pwm (double d)   {   Range 0.0 to 1.0
-    This PWM used to limit max duty ratio of alternator field energisation.
-    With R25=33k and C4=100n controlling ramp input to CS pin of MCP1630 (not MCP1630V),
-    ramp terminates fet 'on' pulse after a max of approx 980 us.
-    With const   int PWM_PERIOD_US = 2000    , duty ratio is thus limited to approx 50% max.
-    This is about right when using 12V alternator on 24V systems
-    A 1.225V reference (U7) is fed to the MCP1630 error amp which compares this to fed-back proportion of system voltage.
-    This adjusts final PWM down to zero % as needed to maintain alternator output voltage.
-*/
-void    set_pwm (double d)   {
-const   double    pwm_factor = MAX_FIELD_PWM * (double)PWM_PERIOD_US;
-    uint32_t i;
-    if  (d < 0.0)
-        d = 0.0;
-    if  (d > 1.0)
-        d = 1.0;
-//    i = (uint32_t)(d * (PWM_PERIOD_US / 2));   //  div 2 when using 12v alternator in 24v system
-    i = (uint32_t)(d * pwm_factor);   //  div 2 when using 12v alternator in 24v system
-//    pc.printf   ("Setting PWM to %d\r\n", i);
-    PWM_OSC_IN.pulsewidth_us  (PWM_PERIOD_US - i);            //  Note PWM is inverted as MCP1630 uses inverted OSC_IN signal
+double  Read_Ammeter    ()
+{
+    return  amp_reading * 93.28; //  Amp range corrected here
 }
 
-/*void    speed_control_factor_set    (struct parameters & a)    {
-    uint32_t v = (uint32_t)a.dbl[0];
-    if  (v > 10)
-        speed_control_factor = v;
-    pc.printf   ("speed_control_factor %d\r\n", speed_control_factor);
-}*/
-
-void    set_throttle_limit    (struct parameters & a)    {
-    if  (a.dbl[0] > 0.01 && a.dbl[0] < 1.001)
-        throttle_limit = a.dbl[0];
-    pc.printf   ("throttle_limit %.2f\r\n", throttle_limit);
-}
 
 void    query_system    (struct parameters & a)    {
     query_toggle = !query_toggle;
-//    pc.printf   ("Stuff about current state of system\r\n");
-//    pc.printf   ("RPM=%d, servo%.2f\r\n", ReadEngineRPM  (), servo_position);
-//    pc.printf   ("RPM=%d\r\n", ReadEngineRPM  ());
+}
+
+void    set_pwm (double d)   {    //  Range 0.0 to 1.0  called from cli
+    Field.set_pwm   (d);
 }
 
-    uint8_t     madetab[340];
-void    maketable   ()  {   //  Uses first 17 nums of user_settings relating to lim to be applied at 0, 500, 1000 --- 8000 RPM
-    double      tabvals[20];
-    double      diff, val = 0.0;
-    uint32_t    tabptr = 0;
-    for (int i = 0; i < 17; i++)    {
-        tabvals[i] = (double)user_settings.rd   (i);
-        pc.printf   ("%d\t%.0f\r\n", i*500, tabvals[i]);
-    }
-    for (int i = 1; i < 17; i++)    {
-        diff = tabvals[i] - tabvals[i - 1];
-        diff /= 20.0;   //  40 entries 25RPM apart per kRPM
-        for (int j = 0; j < 20; j++)    {
-//            pc.printf   ("%.0f\t", val);
-            madetab[tabptr++] = (uint8_t) val;
-            val += diff;
-        }
-    }
-    pc.printf   ("\r\nEnd of table creation with tabptr = %d\r\n", tabptr);
-    while   (tabptr < 340)
-        madetab[tabptr++] = (uint8_t) val;
+double  get_temperature ()  {
+    return  (double) temp_sensor;
+}
+
+void    maketable   ()  {
+    Field.maketable ();
 }
 
+int32_t set_engine_RPM_lit  (uint32_t   RPMrequest) {   //  Returns actual speed
+    return  Engine.set_RPM_literal  (RPMrequest);
+}
 
-/**void    set_pwm_limit   ()  {       //  May 2020
-    *   
-    *   Uses pure look up table to tailor pwm limit according to engine speed
-    *   
-    *   
-    *   
-    *   
-*/
-void    set_pwm_limit   (uint32_t    rpm)  {       //  May 2020
-//const   uint8_t    pwmtab  []  =   unsigned char array of percentages 0 to 99, spaced at 25RPM intervals
-/*const   uint8_t    pwmtab  []  =    {
-    02,02,02,02,02,02,02,02,  //  0   -   0175RPM   //  Slightly above 0 just to see signal on scope
-    02,02,02,02,02,02,02,02,  //  0200 -  0375RPM
-    02,02,02,02,02,02,02,02,  //  0400 -  0575RPM
-    02,02,02,02,02,02,02,02,  //  0600 -  0775RPM
-    02,02,02,02,02,02,02,02,  //  0800 -  0975RPM
-    02,02,02,02,02,02,02,02,  //  1000 -  1175RPM
-    02,02,02,02,02,02,02,02,  //  1200 -  1375RPM
-    02,02,02,02,02,02,02,02,  //  1400 -  1575RPM
-    02,03,04,05,06,07, 8, 9,  //  1600 -  1775RPM
-    10,11,12,13,14,15,16,17,  //  1800 -  1975RPM
-    18,19,20,21,22,23,24,25,  //  2000 -  2175RPM
-    26,27,28,29,30,31,32,33,  //  2200 -  2375RPM
-    34,35,36,37,38,39,40,40,  //  2400 -  2575RPM
-    41,41,41,42,42,42,43,43,  //  2600 -  2775RPM
-    43,44,44,44,45,45,45,46,  //  2800 -  2975RPM
-    46,46,47,47,47,48,48,48,  //  3000 -  3175RPM
-    49,49,49,50,50,50,51,51,  //  3200 -  3375RPM
-    52,52,52,53,53,53,54,54,  //  3400 -  3575RPM
-    54,55,55,55,56,56,56,57,  //  3600 -  3775RPM
-    57,57,58,58,58,59,59,59,  //  3800 -  3975RPM
-    60,60,60,61,61,61,62,62,  //  4000 -  4175RPM
-    62,63,63,63,64,64,64,65,  //  4200 -  4375RPM
-    65,65,66,66,66,67,67,67,  //  4400 -  4575RPM
-    68,68,68,69,69,69,70,70,  //  4600 -  4775RPM
-    71,71,72,72,73,73,74,74,  //  4800 -  4975RPM
-    75,75,76,76,77,77,78,78,  //  5000 -  5175RPM
-    79,79,80,80,81,81,82,82,  //  5200 -  5375RPM
+int32_t set_engine_RPM_pct  (uint32_t   RPMrequest) {   //  Returns actual speed
+    return  Engine.set_RPM_percent  (RPMrequest);
+}
 
-    83,83,84,84,85,85,86,86,  //  5400 -  5575RPM
-    87,87,88,88,89,89,90,90,  //  5600 -  5775RPM
-    91,91,92,92,93,93,94,94,  //  5800 -  5975RPM
-    95,95,96,96,97,97,98,98,  //  6000 -  6175RPM
-    99,99,99,99,99,99,99,99,  //  6200 -  6375RPM
-    99,99,99,99,99,99,99,99,  //  6400 -  6575RPM
-    99,99,99,99,99,99,99,99,  //  6600 -  6775RPM
-    99,99,99,99,99,99,99,99,  //  6800 -  6975RPM
-    99,99,99,99,99,99,99,99,  //  7000 -  7175RPM
-    99,99,99,99,99,99,99,99,  //  7200 -  7375RPM
-    99,99,99,99,99,99,99,99,  //  7400 -  7575RPM
-    99,99,99,99,99,99,99,99,  //  7600 -  7775RPM
-    99,99,99,99,99,99,99,99,  //  7800 -  7975RPM
-    99,99,99,99,99,99,99,99,  //  8000 -  8175RPM
-    }  ;
-*/
-//    uint32_t    rpm = ReadEngineRPM  ();
-    static  uint32_t    oldpcent = 1000;
-    uint32_t    index, pcent;
-    double  pwm = 0.0;
-    if  (rpm > 8000)
-        rpm = 8000;
-    index = rpm / 25;  //  to fit lut spacing of 25rpm intervals, turns rpm into index
-//    pcent = pwmtab[index];
-    pcent = madetab[index];
-    if  (pcent != oldpcent) {
-        oldpcent = pcent;
-        pwm = (double)pcent;
-        pwm /= 99.0;
-        set_pwm (pwm);
+void    auto_test_initiate  (int bulb_count)  {
+    if  (Engine.running())  {
+        auto_test_flag = true;
+        pc.printf   ("Requesting Auto-Test for load of %d lamps\r\n", bulb_count);
+    }
+    else    {
+        pc.printf   ("Engine not running. Can't perform auto test\r\n");
+        auto_test_flag = false;
     }
 }
 
+void    is_eng_running  ()  {
+    pc.printf   ("Engine%sRunning\r\n", Engine.running() ? " IS " : " NOT ");
+}
+
 extern  void    command_line_interpreter    ()  ;   //  Comms with optional pc or device using serial port through board USB socket
-extern  bool    i2c_init    ()  ;
-extern  int     check_24LC64    ()  ;
+
+enum    {AUTO_TEST_INACTIVE, AUTO_TEST_BEGIN, AUTO_TEST_ABORT, AUTO_TEST_IN_PROGRESS    }   ;
 
 //  Programme Entry Point
 int main()
 {
     const   double  filt = 0.2;
+    const   double  ampfilt = 0.2;
+    const   double  vfilt = 0.2;
     //  local variable declarations
-//    double      revs_error;
-    
-    int32_t    RPM_ave = 0, RPM_filt = 0, RPM_tmp;
-//    int32_t irevs_error;
-    uint32_t ticks25Hz = 0;
-    
-    pulse_tacho.fall        (&ISR_magneto_tacho); //    1 pulse per engine rev
-    VEXT.rise               (&ISR_VEXT_rise);   //  Handles - MCP1630 has just turned mosfet on
-    VEXT.fall               (&ISR_VEXT_fall);   //  Handles - MCP1630 has just turned mosfet off
+    double  driver_pot = 0.0, dtmp;
+    int32_t temp, startup_delay, print_position = 0;
+    int32_t field_pct = 0, auto_test_timer = 0, auto_test_state = AUTO_TEST_INACTIVE, auto_test_step = 0;
+    bool    up_and_running = false;
+    char    text[64];
+
     microsecs.reset()   ;   //  timer = 0
     microsecs.start ()  ;   //  64 bit, counts micro seconds and times out in half million years
 
-    PWM_OSC_IN.period_us      (PWM_PERIOD_US); //  about 313Hz * 2
-//  PROBLEM using same pwm, common prescaler, can't update servo that fast, can't pwm field that slow.
-
-    set_pwm (0.02);     //  set_pwm(0.02) good for production. Set higher for test
-
-#ifdef  TARGET_NUCLEO_F401RE    //
-    A_OUT.period_us     (100);  //  pwm as analogue out
-    A_OUT.pulsewidth_us (19);
+#ifdef GPS_
+    gps_mod gps;
 #endif
-    Throttle    = servo_position;
-//    pc.printf   ("\r\n\n\n\n\nAlternator Regulator 2020, Jon Freeman, SystemCoreClock=%d\r\n", SystemCoreClock);
-    pc.printf   ("\r\n\n\n\n\nAlternator Regulator 2020, Jon Freeman\r\n");
-    if  (!i2c_init())
-        pc.printf   ("i2c bus failed init\r\n");
-    pc.printf   ("check_24LC64 returned 0x%x\r\n", check_24LC64());
-    user_settings.load   ()  ;   //  Fetch values from eeprom, also builds table of speed -> pwm lookups
-//    pc.printf   ("Loaded\r\n");
+//- Clear the screen, move to (0,0):
+//  \033[2J
+    pc.printf   ("\033[2JAlternator Regulator 2020, Jon Freeman\r\n");
+    user_settings.load   ()  ;   //  Fetch values from eeprom
     //  Setup Complete ! Can now start main control forever loop.
     loop_timer.attach_us    (&ISR_fast_interrupt, MAIN_LOOP_REPEAT_TIME_US / 10);    //  Start periodic interrupt generator 1000us at Feb 2020
 
-    maketable   ();
+    Field.maketable ()  ;   //  Here to ensure eeprom has been setup
+    Field.set_for_speed (0);
+    Engine.Speed_Control (((double)user_settings.rd(WARMUP_SERVO_POS)) / 100.0);
+    startup_delay = user_settings.rd(WARM_UP_DELAY);
+    pc.printf   ("Operating Mode is [%s]\r\n", get_mode_text    (user_settings.rd(OP_MODE)));
+
 
 //***** START OF MAIN LOOP
     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
+#ifdef  GPS_
+            while   (gps_module.readable())
+                pc.putc (gps_module.getc());
+#endif
             command_line_interpreter    ()  ;   //  Proceed beyond here once loop_timer ticker ISR has set loop_flag true
-            if  (flag_V_rd) {
-                flag_V_rd = false;
-                volt_reading    >>= 1;                                 //  Result = Result / 2
-                volt_reading    += Ain_SystemVolts.read_u16    ();     //  Result = Result + New Reading
+            //  A to D converters all read at 100 Hz
+            if  (flag_link_V_rd) {
+                flag_link_V_rd = false;
+                link_volt_reading    *= (1.0 - vfilt);                                 //
+                link_volt_reading    += vfilt * (double) Ain_Link_Volts.read();     //  Volt fiddle factor NOT corrected here
             }
             if  (flag_Pot_rd)   {
                 flag_Pot_rd = false;
-                dpd *= (1.0 - filt);
-                dpd += filt * (Driver_Pot * 1.5);     //  Includes bodge around zener over-clipping input
-                driver_reading  >>= 1;                                 //  Result = Result / 2
-                driver_reading  += Driver_Pot.read_u16();
+                driver_pot *= (1.0 - filt);
+                driver_pot += filt * ((double)Driver_Pot.read() * 1.5);     //  Includes bodge around zener over-clipping input
+            }
+            if  (flag_A_rd) {
+                flag_A_rd = false;
+                amp_reading *= (1.0 - ampfilt);
+                amp_reading += ampfilt * ((double) Ammeter_In.read() - 0.5); //  Amp range NOT corrected here
+            }
+            if  (flag_field_V_rd) {
+                flag_field_V_rd = false;
+                field_volt_reading    *= (1.0 - vfilt);                                 //
+                field_volt_reading    += vfilt * (double) Field_Supply_V.read();     //  Volt fiddle factor NOT corrected here
             }
         }               //  Jun 2019 pass here 100 times per sec
 //  BEGIN 100Hz stuff
-        loop_flag = false;              //  Clear flag set by ticker interrupt handler
-        
-        //  Three variations on engine rpm.
-        RPM_tmp = ReadEngineRPM ();
-        RPM_ave += RPM_tmp;     //  Rising sum needs dividing and resetting to 0 when used
-        RPM_filt += RPM_tmp;
-        RPM_filt >>= 1;
-
-        set_pwm_limit   (RPM_tmp);     //  according to RPM
-        
+        loop_flag = false;          //  Clear flag set by ticker interrupt handler
+        Engine.manager_core   ();   //  This belongs right here, update regularly, keeps 'filtered()' fresh and keeps engine to set rpm
+#ifdef GPS_
+        gps.update  ();
+#endif
 //  END 100Hz stuff
         if  (flag_25Hz)  {
             flag_25Hz = false;
@@ -600,47 +369,179 @@
 //  BEGIN   12.5Hz stuff
             flag_12Hz5 = !flag_12Hz5;
             if  (flag_12Hz5)  {   //  Do any even stuff to be done 12.5 times per second
-                throttle_setter();
-/*#ifdef  SPEED_CONTROL_ENABLE
-                if  (RPM_demand < TICKOVER_RPM)
-                    servo_position = Throttle = 0.0;
-                else    {
-                    RPM_ave /= 8;
-//                    irevs_error = RPM_demand - ReadEngineRPM  ();
-                    irevs_error = RPM_demand - RPM_filt;
-                    revs_error = (double) irevs_error;
-                    if  (abs(revs_error) > 3.0)    {    //  if speed error > 3rpm, tweak, otherwise deadband
-                        //servo_position += (revs_error / 7500.0);
-                        servo_position += (revs_error / speed_control_factor);
-                        servo_position = normalise(&servo_position);
-                        if  (servo_position < 0.0 || servo_position > 1.0)
-                            pc.printf   ("servo_position error %f\r\n", servo_position);
-                        if  (servo_position > throttle_limit)
-                            servo_position = throttle_limit;
-                        Throttle = servo_position;
+//                if  (up_and_running && Engine.running())  {
+                if  (up_and_running)  {
+                    switch  (user_settings.rd(OP_MODE))  {
+/*
+enum    {SAFE_NOTHING,          //  
+        POT_SERVO_DIRECT,       //  
+        VARIABLE_VOLTAGE,       //  Batteryless, controllerless Low Cost Loco - alternator connects direct to DC motors
+        FIXED_VOLTAGE,          //  
+        ENG_REVS_CTRL,          //  
+        POT_SETS_ENGINE_RPM,    //  
+        CURRENT_FEEDBACK_CTRL,  //  
+        AUTO_TEST            }; //  
+    "0\tSafe nothing mode for cli cmd testing",
+    "1\tPot to Servo direct, field OFF",
+    "2\tVariable voltage",
+    "3\tFixed voltage",
+    "4\tEngine Revs Control",
+    "5\tSet Engine to Driver's Pot",
+    "6\tControl Engine by Current Load",
+*/
+                        case    SAFE_NOTHING:           //  Safe nothing mode for cli cmd testing
+                                    //  Use this to test command line commands e.g. Speed_Control, direct field setting etc
+                            break;
+
+                        case    POT_SERVO_DIRECT:       //  Driver_pot --> servo direct.  Field OFF
+                            Engine.Speed_Control    (driver_pot);
+                            Field.set_for_speed   (0);     //  Safe, no output
+                            break;
+
+                        case    VARIABLE_VOLTAGE:                          //  Variable Voltage
+                            Engine.Speed_Control    (driver_pot);   //  Driver_pot --> servo direct.  Field ON
+                            if  (driver_pot > DRIVER_NEUTRAL)   // if pot not close to zero
+                                Field.set_for_speed   (Engine.RPM_latest());     //  according to RPM
+                            else
+                                Field.set_for_speed   (0);     //  Field OFF
+                            break;
+
+                        case    FIXED_VOLTAGE:          //  Fixed Voltage
+                            Field.set_for_speed   (Engine.RPM_latest());     //  according to RPM
+                            break;
+
+                        case    ENG_REVS_CTRL:          //  Engine revs control - Pot to control revs over range tickover to MAX_RPM_LIMIT
+                            Field.set_for_speed (Engine.RPM_latest());
+                            break;
+
+                        case    POT_SETS_ENGINE_RPM:    //  Set engine to driver pot
+                            dtmp = driver_pot * (MAX_RPM_LIMIT - TICKOVER_RPM) + TICKOVER_RPM;
+                            temp = (int32_t) dtmp;
+                            Engine.set_RPM_literal  (temp);    //  this sets engine speed controller
+                            Field.set_for_speed   (Engine.RPM_latest());     //  according to RPM
+                            break;
+
+//                        case    CURRENT_MODE:  //  Set engine speed determined by current drawn
+                        case    CURRENT_FEEDBACK_CTRL:  //  Set engine speed determined by current drawn
+                            temp = 0;   //  an integer.  Engine set to tickover when no power demand
+                            if  (driver_pot > DRIVER_NEUTRAL)   // if pot not close to zero
+                                temp = 1 + (int32_t)abs(Read_Ammeter() * 8.0); //  Sets max amps to 100 / 8.0
+                            Engine.set_RPM_percent  (temp);    //  this sets engine speed controller
+                            Field.set_for_speed (Engine.RPM_latest());    //  according to RPM
+                            break;
+
+                        case    AUTO_TEST:  //  cli command may initiate test sequence implemented here.  Wait for flag to proceed.
+                            switch  (auto_test_state)   {
+                                case    AUTO_TEST_INACTIVE:
+                                    if  (auto_test_flag)    //  cli has requested auto test sequence
+                                        auto_test_state = AUTO_TEST_BEGIN;
+                                    break;
+
+                                case    AUTO_TEST_BEGIN:    //  set engine, field etc, then initiate settling time delay
+                                    Engine.set_RPM_percent  (1);    //  this sets engine speed controller for min useful revs
+                                    Field.set_for_speed (0);
+                                    pc.printf   ("Starting auto test sequence, user field limit values :\r\n");
+                                    for (int i = 0; i < 21; i++)
+                                        pc.printf   ("%d, ", user_settings.rd(i));
+                                    pc.printf   ("\r\n");
+                                    auto_test_step  = 0;
+                                    auto_test_timer = 0;
+                                    auto_test_state = AUTO_TEST_IN_PROGRESS;
+                                    break;
+
+                                case    AUTO_TEST_IN_PROGRESS:  //  take sets of readings, then timeout back to INACTIVE
+                                    field_pct = Field.set_for_speed (Engine.RPM_latest());    //  according to RPM
+                                    switch  (auto_test_timer++) {   //  When active, get here @ 12.5Hz
+                                        case    30: case    40: case    50: case    60:  //  take readings at these times
+                                        case    35: case    45: case    55: case    65:
+//                                            pc.printf   ("\tTaking auto_test readings %d, Volts, Amps, RPM req, RPM got, servo position, measured duty ratio\r\n", auto_test_timer);
+                                            /*
+                                            Need to collect here,
+                                            Volts, Amps, RPM got latest, RPM got filtered, servo position, measured duty ratio, lut pcent
+                                            */
+                                            pc.printf   ("\t%.2f, %.2f, %.2f, %d, %d, %.2f, %.3f, %d\r\n", Read_Link_Volts(), Read_Field_Volts(), 
+                                                                Read_Ammeter(), Engine.RPM_latest(), Engine.RPM_filtered(),
+                                                                Engine.get_servo_position(), Field.get_duty_ratio(), field_pct);
+                                            break;
+                                        case    66:     //  After final set of readings
+                                            auto_test_step++;
+                                            if  (auto_test_step > 10)
+                                                auto_test_state = AUTO_TEST_ABORT;
+                                            else    {   //  set conditions for next set of readings
+                                                temp = Engine.RPM_percent_to_actual(auto_test_step * 10);
+                                                Engine.set_RPM_percent  (auto_test_step * 10);
+                                                auto_test_timer = 0;
+                                                pc.printf   ("Setting rig for %d percent, %d RPM\r\n", auto_test_step * 10, temp);
+                                            }
+                                            break;
+                                        default:
+                                            break;
+                                    }
+                                    break;
+
+                                case    AUTO_TEST_ABORT:    //  Here at test end or because auto_test_flag made false somewhere somehow
+                                    Engine.set_RPM_percent  (0);    //  this sets engine speed controller
+                                    Field.set_for_speed (0);
+                                    auto_test_state = AUTO_TEST_INACTIVE;
+                                    auto_test_flag = false;
+                                    pc.printf   ("Ending auto test sequence\r\n");
+                                    break;
+                                default:
+                                    break;
+                            }   //  END OF SWITCH auto_test_state
+                            break;
+                        default:
+                            user_settings.wr    (0, OP_MODE); //  set to safe non-mode 0
+                            break;
                     }
                 }
-                RPM_ave = 0;    //  Reset needed
-#endif  */
+                else    {   //  Engine not running
+                    Field.set_for_speed   (0);     //  according to RPM
+                }
             }
             else    {               //  Do odd 12.5 times per sec stuff
                 flag_12Hz5  = false;
                 myled = !myled;
-//                LocalCom.printf ("%d\r\n", volt_reading);
             }   //  End of if(flag_12Hz5)
 //  END 12.5Hz stuff
-            ticks25Hz++;    //  advances @ 25Hz
-            if  (ticks25Hz > 24) {   //  once per sec stuff
+            if  (flag_1Hz)  {
 //  BEGIN   1Hz stuff
-                ticks25Hz = 0;
-//                secs++;
+                flag_1Hz = false;
+                if  (!up_and_running)   {
+                    if  (startup_delay == 0)    {
+                        up_and_running = true;
+                        pc.printf   ("Warmup ended, starting proper ops\r\n");
+                        Engine.Speed_Control (0.0);
+                    }
+                    else    {
+                        pc.printf   ("In Startup warmup delay %d\r", startup_delay--);
+                    }
+                }
                 if  (query_toggle)  {
-                    pc.printf   ("V = %.2f\tRPM = %u\tservo%.2f    \r", Read_BatteryVolts(), /*amp_reading, */ReadEngineRPM  (), servo_position);
-//                    pc.printf   ("\tRPM = %u  (time %u seconds)      \r", ReadEngineRPM  (), (uint32_t)(microsecs.read_high_resolution_us() / 1000000));
+                    sprintf (text, "\033[%d;0HI=%.1fA, V=%.2fV, ", 20 + print_position, Read_Ammeter() , Read_Link_Volts());
+                    pc.printf   ("%sRPM %d, rpm_set %d, pot %.3f, servo %.3f  \r\n", text, Engine.RPM_latest(), Engine.get_RPM_requested (), driver_pot, Engine.get_servo_position());
+                    print_position++;
+                    if  (print_position > 10)
+                        print_position = 0;
                 }
+//printf("\033[6;3HHello\n");
+//                pc.printf   ("\033[0;1HSecs %d  \r\n", seconds);
+//                pc.printf   ("Temp = %.1f\r", get_temperature());
+//            gps.update();
+#ifdef GPS_
+            if  (gps.new_data())    {
+                myled = !myled;
+                pc.printf("\033[0;1H%s, %s, %s      \r\n", gps.time(), gps.latitude(), gps.longitude());
+                pc.printf("alt ^%s^, sats %s lat mer %f, lon mer %f     \r\n", gps.altitude(), gps.sat_count(), gps.lat_merged(), gps.lon_merged());
+                pc.printf("%s, heading %s, mph %s      \r\n", gps.date(), gps.heading(), gps.mph());
+                pc.printf("%s\r\n   \n   .\r\n   ", gps.message(1));
+            }
+#endif
+//            while   (gps_module.readable())
+//                pc.putc (gps_module.getc());
 //  END 1Hz stuff
             }   //  eo once per second stuff
         }   //  End of 100Hz stuff
     }       //  End of main programme loop
 }           //  End of main function - end of programme
-//***** END OF MAIN LOOP
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rpm.cpp	Mon Jul 27 08:44:59 2020 +0000
@@ -0,0 +1,161 @@
+#include "mbed.h"
+#include "rpm.h"
+#include "Alternator.h"
+//#include "BufferedSerial.h"
+//extern  BufferedSerial pc;
+extern  ee_settings_2020 user_settings;
+
+Engine_Manager::Engine_Manager    (PinName ms, PinName cm, PinName for_servo) : MagnetoSignal(ms), CleanedMagneto(cm), Speed_ControlServo(for_servo)
+{
+    //      Constructor
+    magneto_stretch     = false;
+    rpm_in_run_range    = false;
+    latest_RPM_reading = core_call_count = 0;
+    last_update_time = 0;
+    filtered_measured_RPM = requested_RPM = 0.0;
+    MagnetoSignal.rise  (callback(this, &Engine_Manager::MagRise));     // Attach handler to the rising interruptIn edge
+    MagnetoSignal.fall  (callback(this, &Engine_Manager::MagFall));     // Attach handler to the rising interruptIn edge
+//    microsecs.reset  ()  ;   //  timer = 0
+//    microsecs.start  ()  ;   //  64 bit, counts micro seconds and times out in half million years
+    servo_position = MIN_WORKING_THROTTLE;
+    Speed_Control    (MIN_WORKING_THROTTLE);
+};
+
+void        Engine_Manager::magneto_timeoutC    ()
+{
+    magneto_stretch = false;    //  Magneto ringing finished by now, re-enable magneto pulse count
+    CleanedMagneto  = 0;    //  Cleaned tacho output brought out to pin to look at with scope
+}
+
+void        Engine_Manager::MagRise    ()
+{
+    if  (!magneto_stretch) {    //  May get this interrupt more than once per magneto pulse, respond to first, lock out subsequent
+        //  until magneto_timeout time has elapsed
+        magneto_stretch = true;     //  Set flag preventing repeat interrupts until after Timeout interrupt handler clears it 5ms later
+        new_time = microsecs.read_high_resolution_us();
+        magt0 = new_time - magt1;    //  microsecs between most recent two sparks
+        magt1 = new_time;    //  actual time microsecs of most recent spark
+        magneto_timoC.attach_us (callback(this, &Engine_Manager::magneto_timeoutC), DEBOUNCE_US);    //  To ignore ringing and multiple counts on magneto output, all settled within about 5ms
+        CleanedMagneto  = 1;    //  Cleaned tacho output brought out to pin to look at with scope
+    }
+}
+
+void        Engine_Manager::MagFall    ()     //  Need only rising or falling edge, not both
+{
+}
+
+void        Engine_Manager::RPM_update    ()
+{
+    last_update_time = microsecs.read_high_resolution_us();
+    time_since_last_spark = last_update_time - magt1;
+    if  (time_since_last_spark > 500000)    {     //  engine probably not running
+        latest_RPM_reading = 0;
+        running_flag = false;
+    } 
+    else    {
+        running_flag = true;
+        latest_RPM_reading = (60000000 / magt0);
+    }
+    filtered_measured_RPM *= (1.0 - RPM_FILTER_FACTOR);
+    filtered_measured_RPM += RPM_FILTER_FACTOR * (double)latest_RPM_reading;
+}
+
+bool        Engine_Manager::running   ()    //  Answers question is engine running?
+{
+    return  running_flag;
+}
+
+uint32_t    Engine_Manager::RPM_latest    ()
+{
+    return  latest_RPM_reading;
+}
+
+uint32_t    Engine_Manager::RPM_filtered  ()
+{
+    return  (uint32_t)filtered_measured_RPM;
+}
+
+void        Engine_Manager::Speed_Control  (double th)       //  Make this private once all throttle refs local
+{
+    if  (user_settings.rd(SERVO_DIR) == 0)
+        Speed_ControlServo   = th;
+    else
+        Speed_ControlServo = 1.0 - th;
+}
+
+uint32_t    Engine_Manager::set_RPM_literal (uint32_t new_rpm)        //  Returns latest measured RPM
+{
+    if  (new_rpm > MAX_RPM)
+        new_rpm = MAX_RPM;
+    if  (new_rpm >= MIN_RPM) {   //  Runnable or make runnable
+        if  (!rpm_in_run_range) {   //  Transition from idle to working revs
+            rpm_in_run_range = true;
+            servo_position  = MIN_WORKING_THROTTLE; //  set throttle to probably somewhere near low useful revs
+            Speed_Control    (MIN_WORKING_THROTTLE);
+        }   //  Endof transition from idle to working revs
+    }       //  Endof Runnable or make runnable
+    else    {   //  requested rpm are below useful, so kill engine
+        if  (rpm_in_run_range)  {   //  detect transition from useful work to idle
+            servo_position = 0.0;
+            Speed_Control    (0.0);
+            rpm_in_run_range = false;
+        }
+    }
+    requested_RPM = (double) new_rpm;
+    return  latest_RPM_reading;
+}
+
+uint32_t    Engine_Manager::set_RPM_percent (uint32_t new_percent)
+{               //  Returns latest measured RPM
+    uint32_t    temp = 0;
+    if  (new_percent > 100) new_percent = 100;
+    if  (new_percent != 0) 
+        temp = MIN_RPM + (((MAX_RPM - MIN_RPM) * new_percent) / 100);
+    return  set_RPM_literal (temp);
+}
+
+uint32_t    Engine_Manager::RPM_percent_to_actual (uint32_t new_percent)
+{               //  Returns RPM percentage converted to actual RPM
+    uint32_t    temp = 0;
+    if  (new_percent > 100) new_percent = 100;
+    if  (new_percent != 0) 
+        temp = MIN_RPM + (((MAX_RPM - MIN_RPM) * new_percent) / 100);
+    return  temp;
+}
+
+uint32_t    Engine_Manager::get_RPM_requested ()        //  Returns latest requested RPM
+{
+    return  (uint32_t) requested_RPM;
+}
+
+double      Engine_Manager::get_servo_position  ()  //  Only call from test printf in main
+{
+    return  servo_position;
+}
+
+void        Engine_Manager::manager_core()      //  Call this at 100 Hz
+{
+    uint32_t    i = ++core_call_count;
+    int32_t     rpm_error;      //  signed
+    RPM_update  ();     //  Updates filtered and unfiltered RPM measured, 100 times per sec
+    i &= 7;
+    switch  (i)  {
+        case    1:   //  Doing this at 12.5 Hz
+            rpm_error = filtered_measured_RPM - requested_RPM;    //  all doubles. Speed error is positive
+            if  (rpm_in_run_range && (abs(rpm_error) > RPM_DEADBAND))  { //  Try to adjust speed if not within margin
+                servo_position -= rpm_error / RPM_ERR_DENOM; //Denominator sets gain, the 'P', Proportional as in PID
+                if  (servo_position < MIN_WORKING_THROTTLE)     //  Keep servo_position within limits
+                    servo_position = MIN_WORKING_THROTTLE;
+                if  (servo_position > MAX_WORKING_THROTTLE)
+                    servo_position = MAX_WORKING_THROTTLE;
+                Speed_Control    (servo_position);   //  Update signal to engine throttle control servo
+            }
+            break;
+        default:
+            break;
+    }
+}
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rpm.h	Mon Jul 27 08:44:59 2020 +0000
@@ -0,0 +1,49 @@
+#include "mbed.h"
+#include "Servo.h"
+extern  Timer   microsecs;      //  64 bit counter, rolls over in half million years
+
+const   uint32_t    TICKOVER_RPM    = 1500; //2500;
+const   uint32_t    MIN_RPM         = 1600; //3600;
+const   uint32_t    MAX_RPM         = 3600; //6500;
+const   uint32_t    MAX_RPM_LIMIT   = 4000; //7500;     //  Used in building lookup table
+//const   double      RPM_ERR_DENOM   = (MAX_RPM_LIMIT * 20.0);   //  Larger -> more stable, Smaller -> faster response
+const   double      RPM_ERR_DENOM   = (MAX_RPM_LIMIT * 12.0);   //  Larger -> more stable, Smaller -> faster response
+//const   uint32_t    RPM_DEADBAND    = (MAX_RPM_LIMIT / 125);     //  Does not attempt to correct speed errors below this
+const   uint32_t    RPM_DEADBAND    = (MAX_RPM_LIMIT / 180);     //  Does not attempt to correct speed errors below this
+const   uint32_t    DEBOUNCE_US     = (45000000 / MAX_RPM);
+
+const   double      RPM_FILTER_FACTOR   = 0.25;
+const   double      MIN_WORKING_THROTTLE   = 0.23;
+const   double      MAX_WORKING_THROTTLE   = 0.99;
+/*  Cleans magneto pulses, calculates and attempts to maintain RPM  */
+class   Engine_Manager    {
+    uint32_t    latest_RPM_reading, core_call_count;
+    uint64_t    magt0, magt1, time_since_last_spark, new_time, last_update_time;
+    double      servo_position, filtered_measured_RPM, requested_RPM;
+    InterruptIn MagnetoSignal;
+    DigitalOut  CleanedMagneto;
+    Servo       Speed_ControlServo;
+//    const uint32_t  debounce;
+    void        RPM_update  ();
+    void        MagRise ();
+    void        MagFall ();
+    void        magneto_timeoutC    ();
+    bool        magneto_stretch;
+    bool        running_flag;
+    bool        rpm_in_run_range;
+    Timeout     magneto_timoC;
+//    Timer       microseconds;
+  public:
+    Engine_Manager    (PinName magneto_signal, PinName cleaned_output, PinName for_servo)  ;   //  
+    void        Speed_Control    (double);
+    bool        running ();
+    void        manager_core();
+    double      get_servo_position  ();
+    uint32_t    RPM_latest  ();
+    uint32_t    RPM_filtered();
+    uint32_t    get_RPM_requested ()  ;     //  Returns latest requested RPM
+    uint32_t    set_RPM_literal (uint32_t);     //  Returns latest measured RPM
+    uint32_t    set_RPM_percent (uint32_t);     //  Returns latest measured RPM
+    uint32_t    RPM_percent_to_actual (uint32_t);     //  Returns RPM actual calculated from percent
+}   ;
+