Jon Freeman / Mbed 2 deprecated Alternator2020_06

Dependencies:   mbed BufferedSerial Servo2 PCT2075 I2CEeprom FastPWM

Revision:
0:77803b3ee157
Child:
1:450090bdb6f4
diff -r 000000000000 -r 77803b3ee157 main.cpp
--- /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