Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: mbed BufferedSerial Servo2 PCT2075 I2CEeprom FastPWM
Diff: main.cpp
- 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