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

Revision:
2:8e7b51353f32
Parent:
1:450090bdb6f4
Child:
3:43cb067ecd00
--- a/main.cpp	Sat Apr 25 15:35:58 2020 +0000
+++ b/main.cpp	Mon Jun 08 13:46:52 2020 +0000
@@ -1,6 +1,14 @@
 #include "mbed.h"
 #include "Alternator.h"
-
+/*
+Test 6th June 2020 - i2c sda=grey, scl=white
+*/
+float  dpd = 0.0;
+/*
+    *   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
+*/
 
 /*
     Alternator Regulator
@@ -17,13 +25,11 @@
         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 < 2000 (or some such) RPM, field current OFF (by pwm 0)
+    Anytime engine revs measured < TICKOVER_RPM (or some such) RPM, field current OFF (by pwm 0)
     
     BEGIN
         Loop forever at 100 Hz   {
             Read engine RPM by monitoring engine tacho signal present on engine On/Off switch line
-            Read alternator output load current
-            Using RPM and current readings, regulate engine rpm via model control servo
             Adjust Alternator field current max limit according to RPM (analogue regulator limits output voltage)
             Measure system voltage (just in case this is ever useful)
             Respond to any commands arriving at serial port (setup and test link to laptop)
@@ -55,16 +61,20 @@
 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
-BufferedSerial  LocalCom    (PA_9, PA_10);  //  New March 2019
+
+
+//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    Ammeter_Ref     (A0);   //  Ref output from ASC709LLFTR used to set ammeter zero (pin 25)
+//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
-AnalogIn    Ext_Rev_Demand  (D3);   //  Servo determines engine revs, servo out to be higher of Ext_Rev_Demand and internal calc
+//AnalogIn    Ext_Rev_Demand  (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
 
@@ -102,6 +112,15 @@
 30  VIN         
 */
 
+//  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                     (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
 //  OUTPUTS :
@@ -122,9 +141,9 @@
 
 //  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    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
 
 /*
@@ -149,29 +168,22 @@
 
 #endif
 
-Timer   microsecs;
+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;
+//const   double  AMPS_CAL = 90.0;
 extern  eeprom_settings user_settings  ;
 //  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    //semaphore           = 0,
-            speed_control_factor= 75000,  //  fiddled from cli to tweak engine speed controller response
+uint32_t
             volt_reading        = 0,    //  Global updated by interrupt driven read of Battery Volts
-            ext_rev_req         = 0,
             driver_reading      = 0,
-            tacho_count         = 0,    //  Global incremented on each transition of InterruptIn pulse_tacho
+//            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      amp_reading         = 0.0,
-            amp_offset          = 0.0,
-            raw_amp_reading     = 0.0,
-            raw_amp_offset      = 0.0;
 double      servo_position = 0.2;   //  set in speed control loop
-//    const double    throttle_limit = 0.4;
 double      throttle_limit = SERVO_MAX;
 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
@@ -179,7 +191,9 @@
 bool        flag_1Hz    = false;    //  As loop_flag but repeats 1 times per sec
 bool        query_toggle    = false;
 
-const int AMP_FILTER_FACTOR    = 6;
+bool        flag_V_rd   = false;
+bool        flag_Pot_rd = false;
+//const int AMP_FILTER_FACTOR    = 6;
 
 /*  End of Global variable declarations */
 
@@ -189,22 +203,24 @@
     Scope_probe = 1;    //  To show how much time spent in interrupt handler
     switch  (t) {
         case    0:
-            volt_reading    >>= 1;                                 //  Result = Result / 2
-            volt_reading    += Ain_SystemVolts.read_u16    ();     //  Result = Result + New Reading
+            flag_V_rd = true;
+//            volt_reading    >>= 1;                                 //  Result = Result / 2
+//            volt_reading    += Ain_SystemVolts.read_u16    ();     //  Result = Result + New Reading
             break;
-        case    1:
-            raw_amp_reading = (double) Ammeter_In.read();
-            break;
+//        case    1:
+//            raw_amp_reading = (double) Ammeter_In.read();
+//            break;
         case    2:
-            raw_amp_offset = Ammeter_Ref.read();    //  Feb 2020 Not convinced this is useful
+            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    3:
+//            ext_rev_req     >>= 1;                                 //  Result = Result / 2
+//            ext_rev_req     += Ext_Rev_Demand.read_u16();
+//            break;
         case    4:
-            driver_reading  >>= 1;                                 //  Result = Result / 2
-            driver_reading  += Driver_Pot.read_u16();
+//            driver_reading  >>= 1;                                 //  Result = Result / 2
+//            driver_reading  += Driver_Pot.read_u16();
 //            break;
 //        case    5:
             loop_flag = true;   //  set flag to allow main programme loop to proceed
@@ -221,80 +237,11 @@
 }
 
 
-
-//  New stuff November 2019
-/**
-*   Obtaining Amps_Deliverable from RPM.
-*   Lucas workshop sheet shows exponential relationship between RPM over threshold, and Amps_Deliverable,
-*   That is Amps_Deliverable rises steeply with RPM, flattening off towards 6000 RPM
-*   Curve modeled by eqn
-*       I_del = I_max (1 - exp(-(RPM-3000)/Const1))       where Const1 = 1000 is a starting point
-*   This is probably fine when driving alternator with BIG engine.
-*   When using small engine, rising load current sags engine RPM.
-*   Using a linear relationship builds in a good safety margin, possible eqn
-*       I_del = I_max (RPM - 3000) / 3000     for use over RPM range 3000-6000
-*/
-const   double  RPM_Threshold   = 3000.0;
-const   double  RPM_Max         = 6000.0;
-//const   double  I_max = 30.0;
-const   double  RPM_Range   = RPM_Max - RPM_Threshold;
-//#define BIG_ENGINE
-
-double  Calculate_Amps_Deliverable (uint32_t RPM)  {
-    double r = (double)RPM - RPM_Threshold;
-    r /= RPM_Range;
-    if  (r < 0.0)
-        r = 0.0;
-    if  (r > 1.0)
-        r = 1.0;
-#ifdef  BIG_ENGINE
-    const   double  Const1 = -3.2;    //  Tweak this to adjust shape of exponential function
-    return  (1.0 - exp(r*Const1));
-#else
-    return  r;
-#endif
-}
-
-class   one_over_s_integrator   {   //  Need this to drive servo  Jan 2020 why?
-    double  internal_integral, max, min, Hz, gain;
-    public:
-    one_over_s_integrator   ()  {   internal_integral = 0.0; max = 1.0; min = -1.0; Hz = 100.0; gain = 1.0;}
-    double  integral    (double input)  ;
-    void    set_max (double)    ;
-    void    set_min (double)    ;
-    void    set_gain (double)    ;
-    void    set_sample_time (double)    ;
-}   ;
-
-void    one_over_s_integrator::set_max (double in)  {
-    max = in;
-}
-void    one_over_s_integrator::set_min (double in)  {
-    min = in;
-}
-void    one_over_s_integrator::set_gain (double in)  {
-    gain = in;
-}
-void    one_over_s_integrator::set_sample_time (double in)  {
-    Hz = 1.0 / in;
-}
-double  one_over_s_integrator::integral (double input)  {
-    internal_integral += gain * input / Hz;     //  100 for 100Hz update rate
-    if  (internal_integral > max)
-        internal_integral = max;
-    if  (internal_integral < min)
-        internal_integral = min;
-    return  internal_integral;
-}
-
-//double  one_over_s  ()
-//  End of New stuff November 2019
-
-
 //  New stuff June 2019
+//  Decent way of measuring engine speed
 bool    magneto_stretch = false;
 Timeout magneto_timo;
-uint32_t magneto_times[8] = {0,0,0,0,0,0,0,0};  //  June 2019, only 2 of these used
+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
 
 
 /**    
@@ -324,54 +271,27 @@
 */
 void    ISR_magneto_tacho   ()  //  This interrupt initiated by rising (or falling) edge of magneto output, (not both)
 {
-    if  (!magneto_stretch)
-    {
-        uint32_t    new_time = microsecs.read_us();
-        if  (new_time < magneto_times[1])      //  rollover detection
-            magneto_times[1] = 0;
+    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_stretch = true;
         magneto_timo.attach_us (&magneto_timeout, 5000);    //  To ignore ringing and multiple counts on magneto output, all settled within about 5ms
-        tacho_count++;
         EngineTachoOut  = 1;    //  Cleaned tacho output brought out to pin to look at with scope
     }
 }
 
 //  Endof New stuff June 2019
 
-const   double  shrink = 0.2;
-/*double  lpf_4th_order_asym    (double input)  {
-*
-*   input is driver's control pot.
-*   This needs regular calling, maybe 8Hz - 32Hz
-*
-*   Output from 4th stage of cascaded Butterworth lpf section
-*   Used to delay rising input to motor controller allowing time for engine revs to rise
-*/
-double  lpf_4th_order_asym    (double input)  {
-    static  double  lpfs[] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
-    if  (input < 0.0)   input = 0.0;
-    if  (input > 1.0)   input = 1.0;
-    lpfs[0] = input;    //  zeroth order filter
-    double tmp;
-    for (int order = 1; order < 5; order++) {
-        tmp = (lpfs[order] * (1.0 - shrink))
-              + (lpfs[order - 1] * shrink);
-        if  (tmp > input)
-            tmp = input;
-        lpfs[order] = tmp;
-    }
-    return  tmp;
-}
-
 
     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
-    uint32_t    tmp = microsecs.read_us();
+    uint64_t    tmp = microsecs.read_high_resolution_us();
     Field.measured_period = tmp - Field.t_on;
     Field.t_on = tmp;
     Field.rise_count++;
@@ -379,7 +299,7 @@
 void    ISR_VEXT_fall    ()  //  InterruptIn interrupt service
 {
     Field.fall_count++;
-    Field.t_off = microsecs.read_us();
+    Field.t_off = microsecs.read_high_resolution_us();
     Field.measured_pw_us = Field.t_off - Field.t_on;
 }
 //  ****    End of Interrupt Service Routines   ****
@@ -392,7 +312,7 @@
 */
 uint32_t    ReadEngineRPM  ()
 {
-    uint32_t time_since_last_spark = microsecs.read_us() - magneto_times[1];
+    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
@@ -410,26 +330,11 @@
     return  rv / 4096.0;
 }
 
-double  Read_AlternatorAmps   ()
-{
-    int32_t diff    = amp_reading - amp_offset;
-    double  amps    = ((double) diff) / (1 << AMP_FILTER_FACTOR);
-    amps -= 365.0;  //  offset probably specific to particular board.
-    amps /= 1433.0;     //  fiddle factor
-    return  amps;
-}
-
 double  Read_BatteryVolts   ()
 {
     return  ((double) volt_reading) / 3282.5;    //  divisor fiddled to make voltage reading correct !
 }
 
-void    Read_Ammeter   (double * p)  
-{
-    p[0] = amp_reading;
-    p[1] = amp_offset;
-}
-
 /**
 void    set_servo   (double p)  {   //  Only for test, called from cli
 */
@@ -445,15 +350,36 @@
     return  * p;
 }
 
-uint32_t    RPM_demand = 0;         //  For test, set from cli 'sv'
-/**
+
+
+//const   double  DRIVER_NEUTRAL = 0.18;
+/**void    throttle_setter ()  {
+    *   
+    *   
+    *   
+    *   
+    *   
+    *   
 */
-void    set_RPM_demand  (uint32_t   d)  {
-    if  (d < 10)
-        d = 10;
-    if  (d > MAX_RPM_LIMIT)
-        d = MAX_RPM_LIMIT;
-    RPM_demand = d;
+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
+        }
+    }
 }
 
 /**void    set_pwm (double d)   {   Range 0.0 to 1.0
@@ -466,22 +392,24 @@
     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_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
 }
 
-void    speed_control_factor_set    (struct parameters & a)    {
+/*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)
@@ -493,6 +421,103 @@
     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  ());
+}
+
+    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;
+}
+
+
+/**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
+
+    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);
+    }
 }
 
 extern  void    command_line_interpreter    ()  ;   //  Comms with optional pc or device using serial port through board USB socket
@@ -502,15 +527,13 @@
 //  Programme Entry Point
 int main()
 {
+    const   double  filt = 0.2;
     //  local variable declarations
-//    double      servo_position = 0.2;   //  set in speed control loop
-    double      revs_error;
-    double      Amps_Deliverable = 0.0;     //  New Nov 2019
-//    double      tempfilt = 0.0, servo_fucker = 0.01;
+//    double      revs_error;
     
     int32_t    RPM_ave = 0, RPM_filt = 0, RPM_tmp;
-    int32_t irevs_error;
-    uint32_t ticks = 0;
+//    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
@@ -521,14 +544,15 @@
     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.
 
-    PWM_OSC_IN.pulsewidth_us  (PWM_PERIOD_US / 2);            //  value is int
-//    PWM_OSC_IN.pulsewidth_us  (PWM_PERIOD_US);            //  value is int
+    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);
 #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, 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());
@@ -537,10 +561,24 @@
     //  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   ();
+
 //***** 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
             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
+            }
+            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();
+            }
         }               //  Jun 2019 pass here 100 times per sec
 //  BEGIN 100Hz stuff
         loop_flag = false;              //  Clear flag set by ticker interrupt handler
@@ -550,21 +588,9 @@
         RPM_ave += RPM_tmp;     //  Rising sum needs dividing and resetting to 0 when used
         RPM_filt += RPM_tmp;
         RPM_filt >>= 1;
-        
-        amp_reading += (raw_amp_reading - 0.5) * AMPS_CAL;
-        amp_reading /= 2.0;
-        amp_offset  += (raw_amp_offset - 0.5) * AMPS_CAL;   //  This reading probably not useful
-        amp_offset /= 2.0;
-        
-        Amps_Deliverable = Calculate_Amps_Deliverable   (ReadEngineRPM  ());    //  Added Nov 2019, not yet used. Returns normalised 0.0 to 1.0
+
+        set_pwm_limit   (RPM_tmp);     //  according to RPM
         
-//        PWM_OSC_IN.pulsewidth_us  (user_settings.get_pwm(ReadEngineRPM()));    //  Update field current limit according to latest measured RPM
-
-//        while   (LocalCom.readable())   {
-//            int q = LocalCom.getc();
-//            //q++;
-//            pc.putc (q);
-//        }
 //  END 100Hz stuff
         if  (flag_25Hz)  {
             flag_25Hz = false;
@@ -574,7 +600,8 @@
 //  BEGIN   12.5Hz stuff
             flag_12Hz5 = !flag_12Hz5;
             if  (flag_12Hz5)  {   //  Do any even stuff to be done 12.5 times per second
-#ifdef  SPEED_CONTROL_ENABLE
+                throttle_setter();
+/*#ifdef  SPEED_CONTROL_ENABLE
                 if  (RPM_demand < TICKOVER_RPM)
                     servo_position = Throttle = 0.0;
                 else    {
@@ -594,35 +621,23 @@
                     }
                 }
                 RPM_ave = 0;    //  Reset needed
-#endif
+#endif  */
             }
             else    {               //  Do odd 12.5 times per sec stuff
                 flag_12Hz5  = false;
                 myled = !myled;
-                LocalCom.printf ("%d\r\n", volt_reading);
-    //void    set_pwm (double d)   {
-                
-    //            set_pwm (user_settings.get_pwm(ReadEngineRPM()));
-                
-    /*            servo_position += servo_fucker;
-                if  (servo_position > 1.0 || servo_position < 0.0)
-                    servo_fucker *= -1.0;
-                Throttle = servo_position;
-    */            
+//                LocalCom.printf ("%d\r\n", volt_reading);
             }   //  End of if(flag_12Hz5)
 //  END 12.5Hz stuff
-            ticks++;    //  advances @ 25Hz
-            if  (ticks > 24) {   //  once per sec stuff
+            ticks25Hz++;    //  advances @ 25Hz
+            if  (ticks25Hz > 24) {   //  once per sec stuff
 //  BEGIN   1Hz stuff
-                ticks = 0;
+                ticks25Hz = 0;
+//                secs++;
                 if  (query_toggle)  {
-                    pc.printf   ("V=%.1f\tI=%.1f\tRPM=%d\tservo%.2f\r\n", Read_BatteryVolts(), amp_reading, ReadEngineRPM  (), servo_position);
+                    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));
                 }
-//                pc.printf   ("Tick %d\r\n", flag_12Hz5);
-//                tempfilt *= 0.99;
-//                tempfilt += Read_AlternatorAmps() * 0.01;
-//                pc.printf   ("RPM %d, err %.1f, s_p %.2f, lut %.3f\r\n", ReadEngineRPM  (), revs_error, servo_position, user_settings.get_pwm(ReadEngineRPM()));
-//                pc.printf   ("Vbat %.2f, servo %.3f, amps %.3f, filt %.3f\r\n", Read_BatteryVolts(), servo_position, Read_AlternatorAmps(), tempfilt);
 //  END 1Hz stuff
             }   //  eo once per second stuff
         }   //  End of 100Hz stuff