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

main.cpp

Committer:
JonFreeman
Date:
2019-06-28
Revision:
0:77803b3ee157
Child:
1:450090bdb6f4

File content as of revision 0:77803b3ee157:

#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