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, committed 2020-06-08
- Comitter:
- JonFreeman
- Date:
- Mon Jun 08 13:46:52 2020 +0000
- Parent:
- 1:450090bdb6f4
- Child:
- 3:43cb067ecd00
- Commit message:
- About to revamp i2c
Changed in this revision
--- a/Alternator.h Sat Apr 25 15:35:58 2020 +0000 +++ b/Alternator.h Mon Jun 08 13:46:52 2020 +0000 @@ -1,38 +1,34 @@ #include "Servo.h" #include "BufferedSerial.h" -#define SPEED_CONTROL_ENABLE // Includes engine revs servo control loop +//#define SPEED_CONTROL_ENABLE // Includes engine revs servo control loop -const int TICKOVER_RPM = 2500; -const int MAX_RPM_LIMIT = 7500; -const double SERVO_MAX = 0.5; -const int eeprom_page = 17; // Determines where in eeprom 'settings' reside +const uint32_t TICKOVER_RPM = 2500; +const uint32_t PWM_OFF_RPM_LIMIT = (TICKOVER_RPM * 9) / 10; +const uint32_t MAX_RPM_LIMIT = 7500; +const double MAX_FIELD_PWM = 0.47; +const double SERVO_MAX = 0.5; +const double DRIVER_NEUTRAL = 0.18; +const uint32_t eeprom_page = 17; // Determines where in eeprom 'settings' reside -const 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 +//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 VEXT_Data { public: - uint32_t t_on, t_off, measured_pw_us, measured_period, rise_count, fall_count; + uint64_t t_on, t_off, measured_pw_us, measured_period, rise_count, fall_count; double duty_cycle () { return (double) measured_pw_us / (double) measured_period; } ; VEXT_Data () { // constructor - t_on = t_off = measured_pw_us = measured_period = rise_count = fall_count = 0; + t_on = t_off = measured_pw_us = measured_period = rise_count = fall_count = 0L; } ; } ; class eeprom_settings { char settings [36]; - double 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 - double get_pwm (int) ; char rd (uint32_t) ; // Read one setup char value from private buffer 'settings' bool rd (char *, uint32_t) ; // Read one setup char value from private buffer 'settings' bool wr (char, uint32_t) ; // Write one setup char value to private buffer 'settings' @@ -50,12 +46,12 @@ const char * t; // description } ; -const int MAX_PARAMS = 10; +const int MAX_PARAMS = 12; // Up from 10 May 2020 struct parameters { int32_t times[50]; int32_t position_in_list, last_time, numof_dbls; double dbl[MAX_PARAMS]; } ; -const int PWM_PERIOD_US = 2400 ; +const int PWM_PERIOD_US = 1800 ; // Was 2400, May want to reduce this, note would require change of resistor value on board
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/FastPWM.lib Mon Jun 08 13:46:52 2020 +0000 @@ -0,0 +1,1 @@ +https://os.mbed.com/users/Sissors/code/FastPWM/#d6c2b73d71f5
--- a/cli.cpp Sat Apr 25 15:35:58 2020 +0000 +++ b/cli.cpp Mon Jun 08 13:46:52 2020 +0000 @@ -15,11 +15,11 @@ //extern int ver, vef, measured_pw_us; extern void set_throttle_limit (struct parameters & a) ; -extern void speed_control_factor_set (struct parameters & a) ; +//extern void speed_control_factor_set (struct parameters & a) ; extern void query_system (struct parameters & a) ; extern uint32_t ReadEngineRPM () ; extern double Read_BatteryVolts () ; -extern void Read_Ammeter (double *) ; +//extern void Read_Ammeter (double *) ; @@ -34,6 +34,32 @@ extern BufferedSerial pc; #endif //extern double test_pot; // These used in knifeandfork code testing only +extern void maketable () ; + +void table_tweak_cmd (struct parameters & a) { // Requires two params. First '20', '25' etc representing hundreds RPM. Second 0 to 99 percent + char txt[100]; + uint32_t d[3]; + txt[0] = 0; + if (a.numof_dbls != 2) + sprintf (txt, "Need 2 params, got %d, ", a.numof_dbls); + else { + d[2] = (uint32_t)a.dbl[0]; + d[0] = d[2] / 5; + d[1] = (uint32_t)a.dbl[1]; + if (d[0] > 16 || d[1] > 99 || d[2] != d[0] * 5) + sprintf (txt + strlen(txt), "Param out of range %d, %d, ", d[2], d[1]); + else { + pc.printf ("Off to reset table %d RPM, %d percent\r\n", d[2] * 100, d[1]); + user_settings.wr ((char)d[1], d[0]); + user_settings.save (); + maketable (); + } + } + if (txt[0]) + pc.printf ("Errors in table_tweak_cmd - %s\r\n", txt); + else + pc.printf ("Good in table_tweak_cmd, RPM=%d, percentage=%d\r\n", d[0] * 500, d[1]); +} //extern int numof_eeprom_options2 ; //extern struct optpar const option_list2[] ; @@ -97,38 +123,39 @@ } -void gpcmd (struct parameters & a) { +/*void gpcmd (struct parameters & a) { pc.printf ("pwm=%.3f\r\n", user_settings.get_pwm ((int)a.dbl[0])); -} +}*/ extern VEXT_Data Field; -void rfcmd (struct parameters & a) { - pc.printf ("Field.measured_period = %d, Field.measured_pw_us = %d, duty_cycle = %.3f\r\n", Field.measured_period, Field.measured_pw_us, Field.duty_cycle()); +void rfcmd (struct parameters & a) { // Note casts all wrong here, values are 64 bit + pc.printf ("Field.measured_period = %u", (uint32_t)Field.measured_period); + pc.printf (", Field.measured_pw_us = %u, duty_cycle = %.3f\r\n", (uint32_t)Field.measured_pw_us, Field.duty_cycle()); } -extern void set_RPM_demand (uint32_t d) ; +//extern void set_RPM_demand (uint32_t d) ; -void set_rpm_cmd (struct parameters & a) { +/*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) { +/*void speedcmd (struct parameters & a) { int s = ReadEngineRPM (); pc.printf ("speed %d, pwm %.3f\r\n", s, user_settings.get_pwm(s)); -} +}*/ void vcmd (struct parameters & a) { pc.printf ("volts %.2f\r\n", Read_BatteryVolts()); } -void icmd (struct parameters & a) { +/*void icmd (struct parameters & a) { double results[4]; //double * ampsptr = Read_Ammeter(results) ; pc.printf ("amps %.3f, offset %.3f\r\n", results[0], results[1]); -} +}*/ extern void set_servo (double p) ; // Only for test, called from cli @@ -159,20 +186,21 @@ struct kb_command const command_list[] = { {"?", "Lists available commands, same as ls", menucmd}, + {"tt", "Table Tweak - 2 params RPM/100, percentage 0-99", table_tweak_cmd}, {"rf", "Check rise and fall on VEXT", rfcmd}, - {"s", "Speed, RPM", speedcmd}, +// {"s", "Speed, RPM", speedcmd}, {"v", "Read Battery volts", vcmd}, - {"i", "Read Ammeter", icmd}, +// {"i", "Read Ammeter", icmd}, {"p", "Set PWM 0 to 2400???", p_cmd}, {"q", "Query system - toggle message stream on/off", query_system}, - {"gp","Get pwm from RPM", gpcmd}, +// {"gp","Get pwm from RPM", gpcmd}, {"mode", "See or set eeprom values", mode19_cmd}, {"nu", "do nothing", null_cmd}, #ifndef SPEED_CONTROL_ENABLE // Includes engine revs servo control loop {"ser","set throttle servo direct 0 - 99", set_servo_cmd}, #endif - {"sf","set speed control factor", speed_control_factor_set}, - {"sv","set engine RPM demand 2500 - 6000", set_rpm_cmd}, +// {"sf","set speed control factor", speed_control_factor_set}, +// {"sv","set engine RPM demand 2500 - 6000", set_rpm_cmd}, {"tl","set throttle_limit 0.0-1.0", set_throttle_limit}, };
--- a/i2c_bit_banged.cpp Sat Apr 25 15:35:58 2020 +0000 +++ b/i2c_bit_banged.cpp Mon Jun 08 13:46:52 2020 +0000 @@ -1,6 +1,16 @@ #include "mbed.h" #include "Alternator.h" + +#define I2CTEST + #ifdef TARGET_NUCLEO_L432KC // +#ifdef I2CTEST +extern Serial pc; +extern I2C i2c; +const int ACK = 1; // but acknowledge is 0, NAK is 1 6/6/2020 think this should be 1 + +#else + extern Serial pc; DigitalInOut SDA (D4); // Horrible bodge to get i2c working using bit banging. DigitalInOut SCL (D5); // DigitalInOut do not work as you might expect. Fine if used only as OpenDrain opuputs though! @@ -13,6 +23,7 @@ extern I2C i2c; const int ACK = 1; // but acknowledge is 0, NAK is 1 #endif +#endif const int _24LC_rd = 0xa1; // set bit 0 for read, clear bit 0 for write const int _24LC_wr = 0xa0; // set bit 0 for read, clear bit 0 for write @@ -22,20 +33,35 @@ 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, 1, "max pwm% @ Eng RPM 0, 0 to 100"}, + {0, 100, 1, "max pwm% @ Eng RPM 500, 0 to 100"}, + {0, 100, 1, "max pwm% @ Eng RPM 1000, 0 to 100"}, + {0, 100, 1, "max pwm% @ Eng RPM 1500, 0 to 100"}, + {0, 100, 1, "max pwm% @ Eng RPM 2000, 0 to 100"}, + {0, 100, 10, "max pwm% @ Eng RPM 2500, 0 to 100"}, + {0, 100, 60, "max pwm% @ Eng RPM 3000, 0 to 100"}, + {0, 100, 70, "max pwm% @ Eng RPM 3500, 0 to 100"}, + {0, 100, 60, "max pwm% @ Eng RPM 4000, 0 to 100"}, + {0, 100, 50, "max pwm% @ Eng RPM 4500, 0 to 100"}, + {0, 100, 40, "max pwm% @ Eng RPM 5000, 0 to 100"}, + {0, 100, 33, "max pwm% @ Eng RPM 5500, 0 to 100"}, + {0, 100, 30, "max pwm% @ Eng RPM 6000, 0 to 100"}, + {0, 100, 30, "max pwm% @ Eng RPM 6500, 0 to 100"}, + {0, 100, 40, "max pwm% @ Eng RPM 7000, 0 to 100"}, + {0, 100, 50, "max pwm% @ Eng RPM 7500, 0 to 100"}, + {0, 100, 60, "max pwm% @ Eng RPM 8000, 0 to 100"}, {0, 100, 0, "Future 2"}, {0, 100, 0, "Future 3"}, {0, 100, 0, "Future 4"}, {0, 100, 0, "Future 5"}, + {0, 100, 0, "Future 6"}, + {0, 100, 0, "Future 7"}, + {0, 100, 0, "Future 8"}, + {0, 100, 0, "Future 9"}, + {0, 100, 0, "Future 10"}, + {0, 100, 0, "Future 11"}, + {0, 100, 0, "Future 12"}, + {0, 100, 0, "Future 13"}, } ; const int numof_eeprom_options2 = sizeof(option_list2) / sizeof (struct optpar); @@ -79,16 +105,16 @@ return true; } -double eeprom_settings::get_pwm (int rpm) { +/*double eeprom_settings::get_pwm (int rpm) { int p = rpm * lut_size; p /= 8000; // 8000 is upper RPM limit, p now scaled to sizeof lut if (p < 0) p = 0; // point to first if (p >= lut_size) p = lut_size - 1; // point to last // pc.printf ("In get_pwm, rpm = %d, lut entry = %d, pwm = %d\r\n", rpm, p, max_pwm_lut[p]); return max_pwm_lut[p]; -} +}*/ -void eeprom_settings::build_lut () { +/*void eeprom_settings::build_lut () { int ptr = 0; int range, i; double acc = 0.0, incr = 0.0; @@ -114,12 +140,12 @@ } 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 (); +//Apr2020 build_lut (); return rv; } @@ -139,6 +165,7 @@ */ bool i2c_init(void) { #ifdef TARGET_NUCLEO_L432KC // +#ifndef I2CTEST SDA.output(); SCL.output(); SDA.mode(OpenDrain); @@ -151,12 +178,12 @@ SCL = 1; wait_us (1); if (SCL_IN == 0 || SDA_IN == 0) return false; - return true; +#endif #endif #ifdef TARGET_NUCLEO_F401RE // // return i2c.init () ; // class has no member "init" +#endif return true; -#endif } /** @@ -171,6 +198,7 @@ */ int i2c_start () { // Should be Both hi, start takes SDA low #ifdef TARGET_NUCLEO_L432KC // +#ifndef I2CTEST int rv = 0; if (SDA_IN == 0 ) { rv |= 1; // Fault - SDA was lo on entry @@ -187,6 +215,11 @@ SCL = 0; wait_us (1); return rv; // Returns 0 on success, 1 with SDA fault, 2 with SCL fault, 3 with SDA and SCL fault +#else + i2c.start () ; + return 0; + +#endif #endif #ifdef TARGET_NUCLEO_F401RE // i2c.start () ; @@ -206,6 +239,10 @@ */ int i2c_stop () { // Should be SDA=0, SCL=1, start takes SDA hi #ifdef TARGET_NUCLEO_L432KC // +#ifdef I2CTEST + i2c.stop () ; + return 0; +#else int rv = 0; SDA = 0; // Pull SDA to 0 wait_us (1); @@ -224,6 +261,7 @@ pc.printf ("SDA stuck lo in stop\r\n"); return rv; // Returns 0 on success, 1 with SDA fault, 2 with SCL fault, 3 with SDA and SCL fault #endif +#endif #ifdef TARGET_NUCLEO_F401RE // i2c.stop () ; return 0; @@ -231,6 +269,7 @@ } #ifdef TARGET_NUCLEO_L432KC // +#ifndef I2CTEST void jclk (int bit) { SCL = bit; wait_us (1); @@ -244,9 +283,13 @@ wait_us (1); } #endif +#endif int i2c_write (int d) { #ifdef TARGET_NUCLEO_L432KC // +#ifdef I2CTEST + return i2c.write (d); +#else int ackbit = 0; if (SCL_IN != 0) { pc.printf ("SCL hi on entry to write\r\n"); @@ -264,6 +307,7 @@ // pc.printf ("wr 0x%x %s\r\n", d, ackbit == 0 ? "ACK" : "nak"); return ackbit; // 0 for acknowledged ACK, 1 for NAK #endif +#endif #ifdef TARGET_NUCLEO_F401RE // return i2c.write (d); #endif @@ -274,6 +318,9 @@ int i2c_read (int acknak) { // acknak indicates if the byte is to be acknowledged (0 = acknowledge) #ifdef TARGET_NUCLEO_L432KC // +#ifdef I2CTEST + return i2c.read (acknak) ; +#else int result = 0; // SCL should be 1 on entry SDA = 1; // Master released SDA if (SCL_IN != 0) pc.printf ("SCL hi arriving at read\r\n"); @@ -292,6 +339,7 @@ // pc.printf ("rd 0x%x %s\r\n", result, acknak == 0 ? "ACK" : "nak"); return result; // Always ? nah #endif +#endif #ifdef TARGET_NUCLEO_F401RE // return i2c.read (acknak) ; #endif
--- 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