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:
Fri Jun 28 19:32:51 2019 +0000
Child:
1:450090bdb6f4
Commit message:
As at end June 2019

Changed in this revision

Alternator.h Show annotated file Show diff for this revision Revisions of this file
Servo.lib 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
i2c_bit_banged.cpp Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed.bld Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Alternator.h	Fri Jun 28 19:32:51 2019 +0000
@@ -0,0 +1,36 @@
+#include    "Servo.h"
+#include    "BufferedSerial.h"
+const   int eeprom_page = 17;   //  Determines where in eeprom 'settings' reside
+
+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
+
+class   eeprom_settings {
+    char        settings    [36];
+    int         max_pwm_lut [lut_size + 4];
+//    bool        rd_24LC64  (int start_addr, char * dest, int length)    ;
+//    bool        wr_24LC64  (int start_addr, char * dest, int length)    ;
+//    bool        set_24LC64_internal_address (int    start_addr) ;
+//    bool        ack_poll    ()  ;
+    void        build_lut   ()  ;
+  public:
+    eeprom_settings (); //  Constructor
+    int         get_pwm (int)   ;
+    char        rd  (uint32_t)  ;           //  Read one setup char value from private buffer 'settings'
+    bool        wr  (char, uint32_t)  ;     //  Write one setup char value to private buffer 'settings'
+    bool        save    ()  ;               //  Write 'settings' buffer to EEPROM
+    bool        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
+}   ;
+
+const   int PWM_PERIOD_US = 3200    ;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Servo.lib	Fri Jun 28 19:32:51 2019 +0000
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/simon/code/Servo/#4e3d7cb4d0a2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cli.cpp	Fri Jun 28 19:32:51 2019 +0000
@@ -0,0 +1,226 @@
+/*
+Command Line Interpreter code module.
+Purpose -
+    Provides easy interface to pc terminal programme for use during programme development, debug etc.
+    Also usable as comms subsystem in finished code for accepting commands, reporting data etc.
+*/
+#include "mbed.h"
+#include "Alternator.h"
+//#include "BufferedSerial.h"
+#include <cctype>
+using namespace std;
+
+extern  eeprom_settings     mode     ;
+//eeprom_settings     mode     ;
+
+extern  int ver, vef, measured_pw_us;
+extern  uint32_t    ReadEngineRPM  ()   ;
+extern  double  Read_BatteryVolts   ()  ;
+
+
+
+
+
+const   int MAX_PARAMS = 10;
+struct  parameters  {
+    int32_t times[50];
+    int32_t position_in_list, last_time, numof_dbls;
+    double  dbl[MAX_PARAMS];
+}   ;
+
+//  WithOUT RTOS
+//extern  BufferedSerial pc;
+extern  Serial pc;
+//extern  BufferedSerial pc;
+extern  double  test_pot;    //  These used in knifeandfork code testing only
+
+//extern  int numof_eeprom_options2    ;
+//extern  struct  optpar const option_list2[]  ;
+extern  struct optpar  option_list2[]    ;
+
+/**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
+{
+    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)
+                    mode.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)
+                    mode.wr(temps[1], PWM_SCALE);
+                break;
+            case    83: //  set to defaults
+                mode.set_defaults   ();
+                break;
+            case    9:      //  9 Save settings
+                mode.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");
+    }
+    pc.printf   ("mode 0\t%s, [%d]\r\n", option_list2[0].t, mode.rd(RPM0));
+    pc.printf   ("mode 1\t%s, [%d]\r\n", option_list2[1].t, mode.rd(RPM1));
+    pc.printf   ("mode 2\t%s, [%d]\r\n", option_list2[2].t, mode.rd(RPM2));
+    pc.printf   ("mode 3\t%s, [%d]\r\n", option_list2[3].t, mode.rd(RPM3));
+    pc.printf   ("mode 4\t%s, [%d]\r\n", option_list2[4].t, mode.rd(RPM4));
+    pc.printf   ("mode 5\t%s, [%d]\r\n", option_list2[5].t, mode.rd(RPM5));
+    pc.printf   ("mode 6\t%s, [%d]\r\n", option_list2[6].t, mode.rd(RPM6));
+    pc.printf   ("mode 7\t%s, [%d]\r\n", option_list2[7].t, mode.rd(RPM7));
+    pc.printf   ("mode 8\t%s, [%d]\r\n", option_list2[8].t, mode.rd(RPM8));
+
+    pc.printf   ("mode 37\t%s, [%d]\r\n", option_list2[PWM_SCALE].t, mode.rd(PWM_SCALE));
+    pc.printf   ("mode 83\tSet to defaults\r\n");
+    pc.printf   ("mode 9\tSave settings\r\r\n");
+
+}
+
+void    gpcmd   (struct parameters & a)   {
+    pc.printf   ("pwm=%d\r\n", mode.get_pwm    ((int)a.dbl[0]));
+}
+
+void    rfcmd   (struct parameters & a)   {
+    pc.printf   ("ver = %d, vef = %d, measured_pw_us = %d\r\n", ver, vef, measured_pw_us);
+}
+
+extern  double  glob_rpm;
+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, %.2f, pwm %d\r\n", s, glob_rpm, mode.get_pwm(s));
+}
+
+void    vcmd    (struct parameters & a)   {
+    pc.printf   ("volts %.2f\r\n", Read_BatteryVolts());
+}
+
+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);
+}
+
+void    null_cmd (struct parameters & a)   {
+    pc.printf   ("At null_cmd, parameters : First %.3f, second %.3f\r\n", a.dbl[0], a.dbl[1]);
+}
+
+void    menucmd (struct parameters & a);
+
+struct kb_command  {
+    const char * cmd_word;         //  points to text e.g. "menu"
+    const char * explan;
+    void (*f)(struct parameters &);   //  points to function
+}  ;
+
+struct  kb_command const command_list[] = {
+    {"?", "Lists available commands, same as ls", menucmd},
+    {"ls", "Lists available commands, same as menu", menucmd},
+    {"rf", "Check rise and fall on VEXT", rfcmd},
+    {"s", "Speed, RPM", speedcmd},
+    {"v", "Read Battery volts", vcmd},
+    {"gp","Get pwm from RPM", gpcmd},
+    {"mode", "See or set eeprom values", mode19_cmd},
+    {"nu", "do nothing", null_cmd},
+    {"ser","set throttle servo direct 0 - 99", set_servo_cmd},
+    {"sv","set engine RPM demand 3000 - 6000", set_rpm_cmd},
+};
+
+const int numof_menu_items = sizeof(command_list) / sizeof(kb_command);
+void    menucmd (struct parameters & a)
+{
+    pc.printf("\r\nIntelligent Alternator Controller - Jon Freeman 2019\r\nAt menucmd function - listing commands:-\r\n");
+    for(int i = 0; i < numof_menu_items; i++)
+        pc.printf("[%s]\t\t%s\r\n", command_list[i].cmd_word, command_list[i].explan);
+    pc.printf("End of List of Commands\r\n");
+}
+
+void    command_line_interpreter    ()
+{
+    const int MAX_CMD_LEN = 120;
+    static  char    cmd_line[MAX_CMD_LEN + 4];
+    static  int     cl_index = 0;
+    int ch;
+    char * pEnd;
+    static struct  parameters  param_block  ;
+    while  (pc.readable()) {
+        ch = tolower(pc.getc());
+       // pc.printf("%c", ch);
+        if  (cl_index > MAX_CMD_LEN)  {   //  trap out stupidly long command lines
+            pc.printf   ("Error!! Stupidly long cmd line\r\n");
+            cl_index = 0;
+        }
+        if  (ch == '\r' || ch >= ' ' && ch <= 'z')
+            pc.printf("%c", ch);
+        else    {                   //  Using <Ctrl>+ 'F', 'B' for Y, 'L', 'R' for X, 'U', 'D' for Z
+            cl_index = 0;           //                 6    2          12   18         21   4
+            pc.printf("[%d]", ch);
+            //nudger  (ch); //  was used on cnc to nudge axes a tad
+        }
+        if(ch != '\r')  //  was this the 'Enter' key?
+            cmd_line[cl_index++] = ch;  //  added char to command being assembled
+        else    {   //  key was CR, may or may not be command to lookup
+            cmd_line[cl_index] = 0; //  null terminate command string
+            if(cl_index)    {   //  If have got some chars to lookup
+                int i, wrdlen;
+                for (i = 0; i < numof_menu_items; i++)   {   //  Look for input match in command list
+                    wrdlen = strlen(command_list[i].cmd_word);
+                    if(strncmp(command_list[i].cmd_word, cmd_line, wrdlen) == 0 && !isalpha(cmd_line[wrdlen]))  {   //  If match found
+                        for (int k = 0; k < MAX_PARAMS; k++)    {
+                            param_block.dbl[k] = 0.0;
+                        }
+                        param_block.position_in_list = i;
+                        param_block.last_time = clock    ();
+                        param_block.numof_dbls = 0;
+                        pEnd = cmd_line + wrdlen;
+                        while   (*pEnd)  {          //  Assemble all numerics as doubles
+                            param_block.dbl[param_block.numof_dbls++] = strtod    (pEnd, &pEnd);
+                            while   (*pEnd && !isdigit(*pEnd) && '-' != *pEnd && '+' != *pEnd)  {   
+                                pEnd++;
+                            }
+                        }
+                        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();
+                        command_list[i].f(param_block);   //  execute command
+                        i = numof_menu_items + 1;    //  to exit for loop
+                    }   //  end of match found
+                }       // End of for numof_menu_items
+                if(i == numof_menu_items)
+                    pc.printf("No Match Found for CMD [%s]\r\n", cmd_line);
+            }           //  End of If have got some chars to lookup
+            pc.printf("\r\n>");
+            cl_index = 0;
+        }               // End of else key was CR, may or may not be command to lookup
+    }                   //  End of while (pc.readable())
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/i2c_bit_banged.cpp	Fri Jun 28 19:32:51 2019 +0000
@@ -0,0 +1,374 @@
+#include "mbed.h"
+#include "Alternator.h"
+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 _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
+const int ACK     = 0;  //  but acknowledge is 0, NAK is 1
+
+
+/*struct  optpar  {
+    int min, max, def;  //  min, max, default
+    const char * t;     //  description
+}   ;*/
+struct  optpar option_list2[] = {
+    {0, 100, 10, "max pwm% @ Eng RPM 0, 0 to 100"},
+    {0, 100, 10, "max pwm% @ Eng RPM 1000, 0 to 100"},
+    {0, 100, 20, "max pwm% @ Eng RPM 2000, 0 to 100"},
+    {0, 100, 30, "max pwm% @ Eng RPM 3000, 0 to 100"},
+    {0, 100, 40, "max pwm% @ Eng RPM 4000, 0 to 100"},
+    {0, 100, 50, "max pwm% @ Eng RPM 5000, 0 to 100"},
+    {0, 100, 50, "max pwm% @ Eng RPM 6000, 0 to 100"},
+    {0, 100, 50, "max pwm% @ Eng RPM 7000, 0 to 100"},
+    {0, 100, 50, "max pwm% @ Eng RPM 8000, 0 to 100"},
+    {0, 100, 50, "Set Overall PWM Scale Factor percent"},
+    {0, 100, 0, "Future 2"},
+    {0, 100, 0, "Future 3"},
+    {0, 100, 0, "Future 4"},
+    {0, 100, 0, "Future 5"},
+}   ;
+
+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 mode  ;
+
+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::wr  (char c, uint32_t i)  {           //  Read one setup char value from private buffer 'settings'
+    if  (i > 31)
+        return  false;
+    settings[i] = c;
+    return  true;
+}
+
+int     eeprom_settings::get_pwm (int rpm)   {
+    int p = rpm * lut_size;
+    p /= 8000;  //  8000 is upper RPM limit
+    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;
+    int base = mode.rd(RPM0) * PWM_PERIOD_US;
+    double  acc, incr;
+    base /= 100;    //  got pwm_pulsewidth of 0 RPM
+    acc = (double) base;
+    pc.printf   ("pwm_period_us ar 0 RPM = %d\r\n", base);
+    for (i = 0; i < 8; i++) {
+        range = mode.rd(i+1) - mode.rd(i);  //  range now change in percent between two 'n'000 RPMs
+        range *= mode.rd(PWM_SCALE);        //  range now 10000 times factor due to percentage twice
+        range *= PWM_PERIOD_US;
+        incr = (double)range;
+        incr /= 10000.0;
+        incr /= lut_seg_size;
+        for(int j = 0; j < lut_seg_size; j++)   {
+            max_pwm_lut[ptr++] = (int)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 < 10; i++)    {
+//            pc.printf   ("%d\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
+    build_lut   ();
+    return  rv;
+}
+
+bool    eeprom_settings::save    ()  {               //  Write 'settings' buffer to EEPROM
+    return  wr_24LC64   (eeprom_page * 32, settings, 32);
+}
+
+
+
+/**
+*   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) {
+    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;
+  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
+    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
+}
+
+/**
+*   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
+    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
+}
+
+void    jclk    (int bit)   {
+    SCL = bit;
+    wait_us (1);
+}
+
+void    jclkout ()  {
+    wait_us (1);
+    SCL = 1;
+    wait_us (1);
+    SCL = 0;
+    wait_us (1);
+}
+
+int i2c_write (int    d)  {
+    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
+}
+
+
+
+
+int i2c_read    (int acknak)  {     //  acknak  indicates if the byte is to be acknowledged (0 = acknowledge)
+    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
+}
+
+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;
+    }
+    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--) {
+        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;
+}
+
+
+
+
+
+
+
+
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Fri Jun 28 19:32:51 2019 +0000
@@ -0,0 +1,446 @@
+#include "mbed.h"
+#include "Alternator.h"
+
+#define MAGNETO_SPEED   //  Selects engine speed input as magneto coil on engine switch line
+#define SPEED_CONTROL_ENABLE    //  Includes engine revs servo control loop
+
+/*
+    Alternator Regulator
+    Jon Freeman
+    June 2019
+    
+    WHAT THIS PROGRAMME DOES - 
+    
+    BEGIN
+        Loop forever at 32 Hz   {
+            Read engine RPM
+            Adjust Alternator field current max limit according to RPM
+            Measure system voltage (just in case this is ever useful)
+            Respond to any commands arriving at serial port (setup and test link to laptop)
+            Flash LED at 8 Hz as proof of life
+        }
+    END
+
+    INPUTS  AnalogIn x 2 - Ammeter chip - current and offset AnalogIns
+    INPUT   AnalogIn - System voltage for info only.
+    INPUT   AnalogIn - ExtRevDemand
+    INPUT   AnalogIn - DriverPot
+    INPUT   Pulse engine speed indicator, speed checked against EEPROM data to select max pwm duty ratio for this speed
+    INPUT   Final pwm gate drive wired back to InterruptIn ** MAYBE USEFUL OR NOT ** Could read this back via serial to laptop
+    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)
+
+/**
+*   Jumpers fitted to small mbed Nucleo boards - D5 - A5 and D4 - A4 CHECK - yes
+*/
+/*
+    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
+BufferedSerial  LocalCom    (PA_9, PA_10);  //  New March 2019
+//  Above combo of Serial and BufferedSerial is the only one to work !
+
+//  INPUTS :
+AnalogIn    Ain_SystemVolts (A0);   //  Brought out to CN8 'AN' A0. Connect 3k3 resistor from A0 to ground.
+AnalogIn    Ammeter_In      (A1);   //  Output of ASC709LLFTR ammeter chip (pin 20)
+AnalogIn    Ammeter_Ref     (A6);   //  Ref output from ASC709LLFTR used to set ammeter zero (pin 25)
+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
+//  Connect 33k resistor from A0 to nom 24 Volt system rail.
+
+//#ifdef  TARGET_NUCLEO_F303K8    //  
+#ifdef  TARGET_NUCLEO_L432KC    //
+/*
+    MODULE PIN USAGE    
+1   PA_9 D1     LocalCom Tx
+2   PA_10 D0    LocalCom Rx
+3   NRST        
+4   GND     
+5   PA12_D2     NEW June 2019 - Output engine tacho cleaned-up
+6   PB_0 D3     AnalogIn Ext_Rev_Demand
+7   PB_7 D4     SDA i2c to 24LC memory
+8   PB_6 D5     SCL i2c to 24LC memory
+9   PB_12 D6    PwmOut     PWM_OSC_IN Timebase for pwm, also determines max duty ratio
+10  N.C.        
+11  N.C.        
+12  PA_8 D9     InterruptIn pulse_tacho from alternator, used to measure rpm
+13  PA_11 D10   
+14  PB_5 D11   
+15  PB_4 D12    
+16  PB_3 D13 LED    Onboard LED
+17  3V3         
+18  AREF        
+19  PA_0 A0     AnalogIn V_Sample system link voltage
+20  PA_1 A1     AnalogIn Ammeter_In
+21  PA_3 A2     PWM analogue out
+22  PA_4 A3     InterruptIn VEXT PWM controller output folded back for cpu to monitor, useful on test to read what pwm required to do what
+23  PA_5 A4     n.c. SDA_IN paralleled to i2c pin, necessary because i2c has to be bit banged
+24  PA_6 A5     n.c. SCL_IN paralleled to i2c pin, necessary because i2c has to be bit banged
+25  PA_7 A6     AnalogIn Ammeter_Ref
+26  PA_2 A7 UART2_TX     Throttle Servo out now on D10, can not use D11, can not use D12 for these
+27  5V          
+28  NRST        
+29  GND         
+30  VIN         
+*/
+
+InterruptIn pulse_tacho     (D9);  //  Signal from 'W' alternator terminal via low pass filter and Schmitt trigger cleanup
+                                    //  NOTE D7 pin was no good for this
+//InterruptIn VEXT    (A3);     //  PWM controller output folded back for cpu to monitor, useful on test to read what pwm required to do what
+InterruptIn VEXT    (D11);     //  PWM controller output folded back for cpu to monitor, useful on test to read what pwm required to do what
+//  OUTPUTS :
+
+//DigitalOut  Scope_probe (D0);   //  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    (A6);   //  Can alter prescaler can not use A5
+PwmOut     PWM_OSC_IN   (D6);   //  Can alter prescaler can not use A5
+PwmOut     A_OUT        (A2);   //  Can alter prescaler can not use A5
+//Servo   Throttle    (A2);   //  Pin A2, PA3
+//Servo   Throttle    (A7);   //  Changed from A2, June 2019
+Servo   Throttle    (D10);   //  Changed from A2, June 2019
+DigitalOut  EngineTachoOut  (D2);   //  New June 2019
+#endif
+Timer   microsecs;
+Ticker  loop_timer;     //  Device to cause periodic interrupts, used to sync iterations of main programme loop - slow
+
+extern  eeprom_settings mode  ;
+//  SYSTEM CONSTANTS
+/*  Please Do Not Alter these */
+const   int     MAIN_LOOP_REPEAT_TIME_US    = 31250;    //  31250 us, with TACHO_TAB_SIZE = 32 means tacho_ticks_per_time is tacho_ticks_per_second
+const   int     MAIN_LOOP_ITERATION_Hz      = 1000000 / MAIN_LOOP_REPEAT_TIME_US;   //  = 32 Hz
+//const   int     FAST_INTERRUPT_RATE         = 3125;
+/*  End of Please Do Not Alter these */
+/*  Global variable declarations */
+uint32_t    //semaphore           = 0,
+            volt_reading        = 0,    //  Global updated by interrupt driven read of Battery Volts
+            amp_reading         = 0,
+            amp_offset          = 0,
+            ext_rev_req         = 0,
+            driver_reading      = 0,
+            tacho_count         = 0,    //  Global incremented on each transition of InterruptIn pulse_tacho
+            tacho_ticks_per_time = 0,   //  Global tacho ticks in most recent (MAIN_LOOP_REPEAT_TIME_US * TACHO_TABLE_SIZE) micro secs
+            sys_timer32Hz       = 0;    //  gets incremented by our Ticker ISR every MAIN_LOOP_REPEAT_TIME_US
+bool        loop_flag   = false;    //  made true in ISR_loop_timer, picked up and made false again in main programme loop
+bool        flag_8Hz    = false;    //  As loop_flag but repeats 8 times per sec
+
+
+const   double  scale = 0.125;
+const   double  shrink_by = 1.0 - scale;
+double  glob_rpm;
+
+/*  End of Global variable declarations */
+
+//void    ISR_fast_interrupt  ()  {   //  here at 10 times main loop repeat rate (i.e. 320Hz)
+void    ISR_fast_interrupt  ()  {   //  here at ** 25 ** times main loop repeat rate (1250us, i.e. 800Hz)
+    static  int t = 0;
+    switch  (t) {
+        case    0:
+            volt_reading    >>= 1;                                 //  Result = Result / 2
+            volt_reading    += Ain_SystemVolts.read_u16    ();     //  Result = Result + New Reading
+            break;
+        case    1:
+            amp_reading     >>= 1;                                 //  Result = Result / 2
+            amp_reading     = Ammeter_In.read_u16();
+            break;
+        case    2:
+            amp_offset      >>= 1;                                 //  Result = Result / 2
+            amp_offset      = Ammeter_Ref.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();
+            break;
+        case    5:
+//            semaphore++;
+            const   int TACHO_TABLE_SIZE    = MAIN_LOOP_ITERATION_Hz;   //  Ensures table contains exactly one seconds worth of samples
+            static  uint32_t    h[TACHO_TABLE_SIZE],    //  circular buffer to contain list of 'tacho_count's
+                    i = 0, last_temp = 0;
+            static  double  rpm_filt = 0.0;
+            double  tmp;
+            
+            uint32_t    temp = tacho_count;         //  Read very latest total pulse count from global tacho_count
+            tmp = (double) (temp - last_temp);
+            last_temp = temp;
+#ifdef  MAGNETO_SPEED
+            tmp *= (scale * 32.0 * 60.0);    //  ???? Is this including alternator poles count ??? Do we need MAGNETO_SPEED included
+#else
+            tmp *= (scale * 32.0 * 60.0 / 12.0);    //  ???? Is this including alternator poles count ??? Do we need MAGNETO_SPEED included
+#endif
+            rpm_filt *= shrink_by;
+            rpm_filt += tmp;
+            glob_rpm = rpm_filt;
+            
+            tacho_ticks_per_time = temp - h[i];     //  latest tacho total pulse count - oldest stored tacho total pulse count
+            h[i]    = temp;                         //  latest overwrites oldest in table
+            i++;                                    //  index to next table position for next time around
+            if  (i >= TACHO_TABLE_SIZE)
+                i = 0;                              //  circular buffer
+            loop_flag = true;   //  set flag to allow main programme loop to proceed
+            sys_timer32Hz++;        //  Just a handy measure of elapsed time for anything to use
+            if  ((sys_timer32Hz & 0x03) == 0)
+                flag_8Hz    = true; //  flag gets set 8 times per sec. Other code may clear flag and make use of this
+            break;
+    }
+    t++;
+    if  (t > 9)
+        t = 0;
+}
+
+
+
+/** void    ISR_loop_timer  ()
+*   This ISR responds to Ticker interrupts at a rate of (probably) 32 times per second (check from constant declarations above)
+*   This ISR sets global flag 'loop_flag' used to synchronise passes around main programme control loop.
+*   Also, updates global int 'tacho_ticks_per_time' to contain total number of transitions from alternator 'W' terminal in
+*   time period from exactly one second ago until now.
+*   Increments global 'sys_timer32Hz', usable anywhere as general measure of elapsed time
+*/
+void    ISR_loop_timer  ()      //  This is Ticker Interrupt Service Routine - loop timer - MAIN_LOOP_REPEAT_TIME_US
+{                               //  Jan 2019 MAIN_LOOP_REPEAT_TIME_US = 31.25 ms
+    const   int TACHO_TABLE_SIZE    = MAIN_LOOP_ITERATION_Hz;   //  Ensures table contains exactly one seconds worth of samples
+    static  uint32_t    h[TACHO_TABLE_SIZE],    //  circular buffer to contain list of 'tacho_count's
+            i = 0, last_temp = 0;
+    static  double  rpm_filt = 0.0;
+    double  tmp;
+    
+    uint32_t    temp = tacho_count;         //  Read very latest total pulse count from global tacho_count
+    tmp = (double) (temp - last_temp);
+    last_temp = temp;
+#ifdef  MAGNETO_SPEED
+    tmp *= (scale * 32.0 * 60.0);    //  ???? Is this including alternator poles count ??? Do we need MAGNETO_SPEED included
+#else
+    tmp *= (scale * 32.0 * 60.0 / 12.0);    //  ???? Is this including alternator poles count ??? Do we need MAGNETO_SPEED included
+#endif
+//    tmp *= (scale * 32.0 * 60.0 / 12.0);
+    rpm_filt *= shrink_by;
+    rpm_filt += tmp;
+    glob_rpm = rpm_filt;
+    
+    tacho_ticks_per_time = temp - h[i];     //  latest tacho total pulse count - oldest stored tacho total pulse count
+    h[i]    = temp;                         //  latest overwrites oldest in table
+    i++;                                    //  index to next table position for next time around
+    if  (i >= TACHO_TABLE_SIZE)
+        i = 0;                              //  circular buffer
+    loop_flag = true;   //  set flag to allow main programme loop to proceed
+    sys_timer32Hz++;        //  Just a handy measure of elapsed time for anything to use
+    if  ((sys_timer32Hz & 0x03) == 0)
+        flag_8Hz    = true; //  flag gets set 8 times per sec. Other code may clear flag and make use of this
+}
+
+
+//  New stuff June 2019
+//uint32_t    magneto_count = 0;
+#ifdef  MAGNETO_SPEED
+bool    magneto_stretch = false;
+Timeout magneto_timo;
+uint32_t magneto_times[8] = {0,0,0,0,0,0,0,0};
+
+void    ISR_magneto_tacho   ()  ;   //  New June 2019
+    //      Engine On/Off switch turns engine off by shorting magneto to ground.
+    //      Therefore have pulse signal one pulse per rev (even tghough 4 stroke, spark delivered at 2 stroke rate)
+    //      Pulse spacing 20ms @ 3000 RPM, 60ms @ 1000 RPM, 6ms @ 10000 RPM
+    
+    //  Relies also on a timeout
+void    magneto_timeout ()
+{
+    magneto_stretch = false;    //  Magneto ringing finished by now, re-enable magneto pulse count
+    EngineTachoOut  = 0;
+}
+
+void    ISR_magneto_tacho   ()  //  Here 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;
+        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;
+    }
+}
+
+#endif
+//  Endof New stuff June 2019
+
+//uint32_t    time_diff;
+/** void    ISR_pulse_tacho ()
+*
+*/
+void    ISR_pulse_tacho ()      //  Interrupt Service Routine - here after each lo to hi and hi to lo transition on pulse_tacho pin
+{
+//    static uint64_t ustot = 0;
+//    uint64_t    new_time = microsecs.read_high_resolution_us();
+    static uint32_t ustot = 0;
+    uint32_t    new_time = microsecs.read_us();
+    if  (new_time < ustot)      //  rollover detection
+        ustot = 0;
+////    time_diff = (uint32_t) new_time - ustot;
+//    time_diff = new_time - ustot;   //  always 0 or positive
+    ustot = new_time;
+    tacho_count++;
+}
+
+uint32_t    t_on = 0, t_off = 0, measured_pw_us = 0;
+int ver = 0, vef = 0;
+void    ISR_VEXT_rise    ()  //  InterruptIn interrupt service
+{   //  Here is possible to read back how regulator has controlled pwm
+    ver++;
+    t_on = microsecs.read_us();
+}
+void    ISR_VEXT_fall    ()  //  InterruptIn interrupt service
+{
+    vef++;
+    t_off = microsecs.read_us();
+    measured_pw_us = t_off - t_on;
+}
+//  ****    End of Interrupt Service Routines   ****
+
+
+/** uint32_t    ReadEngineRPM  ()
+*   System timers arranged such that tacho_ticks_per_time contains most up to the moment count of tacho ticks per second.
+*   This * 60 / number of alternator poles gives Revs Per Minute
+*   Band pass filter alternator phase output - LF rolloff about 50Hz, HF rolloff about 1500Hz
+*/
+uint32_t    ReadEngineRPM  ()
+{
+#ifdef  MAGNETO_SPEED
+    uint32_t time_since_last_spark = microsecs.read_us() - magneto_times[1];
+    if  (time_since_last_spark > 50000)     //  if engine probably stopped, return old method RPM
+        return  tacho_ticks_per_time * 60;          //  1 pulse per rev from magneto
+    return  (60000000 / magneto_times[0]);  //  60 million / microsecs between two most recent sparks
+#else
+    return  tacho_ticks_per_time * 60 / 12;   //  Numof alternator poles, 12, factored in.
+#endif
+}
+
+
+double  Read_BatteryVolts   ()
+{
+    return  (double) volt_reading / 1626.0;    //  divisor fiddled to make voltage reading correct !
+}
+
+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;
+}
+
+uint32_t    RPM_demand = 0;         //  For test, set from cli
+void    set_RPM_demand  (uint32_t   d)  {
+    if  (d < 10)
+        d = 10;
+    if  (d > 5600)
+        d = 5600;
+    RPM_demand = d;
+}
+
+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    ()  ;
+
+//  Programme Entry Point
+int main()
+{
+    //  local variable declarations
+    double      servo_position = 0.2;   //  set in speed control loop
+    double      revs_error;
+    int irevs_error;
+    int ticks = 0;
+    const double    throttle_limit = 0.4;
+    
+    loop_timer.attach_us    (&ISR_loop_timer, MAIN_LOOP_REPEAT_TIME_US);    //  Start periodic interrupt generator
+#ifdef    MAGNETO_SPEED
+    pc.printf   ("Magneto Mode\r\n");
+    pulse_tacho.fall        (&ISR_magneto_tacho); //    1 pulse per engine rev
+#else
+    pc.printf   ("Alternator W signal Mode\r\n");
+    pulse_tacho.rise        (&ISR_pulse_tacho); //  Handles - Transition on filtered input version of alternator phase output
+    pulse_tacho.fall        (&ISR_pulse_tacho); //
+#endif
+    VEXT.rise               (&ISR_VEXT_rise);   //  Handles - MCP1630 has just turned mosfet on
+    VEXT.fall               (&ISR_VEXT_fall);   //  Handles - MCP1630 has just turned mosfet off
+    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
+    PWM_OSC_IN.pulsewidth_us  (9);            //  value is int
+    A_OUT.period_us     (100);
+    A_OUT.pulsewidth_us (19);
+    Throttle    = servo_position;
+    pc.printf   ("\r\n\n\n\n\nAlternator Regulator 2019, Jon Freeman, SystemCoreClock=%d\r\n", SystemCoreClock);
+    if  (!i2c_init())
+        pc.printf   ("i2c bus failed init\r\n");
+    //  end of local variable declarations
+    pc.printf   ("check_24LC64 returned 0x%x\r\n", check_24LC64());
+    mode.load   ()  ;   //  Fetch values from eeprom, also builds table of speed -> pwm lookups
+    
+    //  Setup Complete ! Can now start main control forever loop.
+
+//***** 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
+        }               //  Jan 2019 pass here 32 times per sec
+        loop_flag = false;              //  Clear flag set by ticker interrupt handler
+#ifdef  SPEED_CONTROL_ENABLE
+//    uint32_t    RPM_demand = 0;         //  For test, set from cli
+//    double      servo_position = 0.0;   //  set in speed control loop
+//    double      revs_error;
+
+//        time_since_last_spark = microsecs.read_us() - magneto_times[1];
+        irevs_error = RPM_demand - ReadEngineRPM  ();
+        revs_error = (double) irevs_error;
+        if  (RPM_demand < 3000)
+            servo_position = Throttle = 0.0;
+        else    {
+            servo_position += (revs_error / 75000.0);
+            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;
+        }
+#endif
+
+        PWM_OSC_IN.pulsewidth_us  (mode.get_pwm((int)glob_rpm));    //  Update field current according to latest measured RPM
+
+//        while   (LocalCom.readable())   {
+//            int q = LocalCom.getc();
+//            //q++;
+//            pc.putc (q);
+//        }
+
+        if  (flag_8Hz)  {   //  Do any stuff to be done 8 times per second
+            flag_8Hz    = false;
+            myled = !myled;
+            LocalCom.printf ("%d\r\n", volt_reading);
+            
+            ticks++;
+            if  (ticks > 7) {   //  once per sec stuff
+                ticks = 0;
+                pc.printf   ("RPM %d, err %.1f, s_p %.2f\r\n", ReadEngineRPM  (), revs_error, servo_position);
+            }   //  eo once per second stuff
+        }   //  End of if(flag_8Hz)
+    }       //  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/mbed.bld	Fri Jun 28 19:32:51 2019 +0000
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/mbed_official/code/mbed/builds/65be27845400
\ No newline at end of file