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
rpm.cpp@5:6ca3e7ffc553, 2020-12-05 (annotated)
- Committer:
- JonFreeman
- Date:
- Sat Dec 05 12:40:17 2020 +0000
- Revision:
- 5:6ca3e7ffc553
- Parent:
- 3:43cb067ecd00
Code for 'Smart Regulator' to August 2020, published as is. Basic low-level functions all thoroughly tested and debugged, top level algorithms have scope for further development - over to you! For help contact jon @ jons-workshop[.com
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
JonFreeman | 3:43cb067ecd00 | 1 | #include "mbed.h" |
JonFreeman | 3:43cb067ecd00 | 2 | #include "rpm.h" |
JonFreeman | 3:43cb067ecd00 | 3 | #include "Alternator.h" |
JonFreeman | 3:43cb067ecd00 | 4 | //#include "BufferedSerial.h" |
JonFreeman | 3:43cb067ecd00 | 5 | //extern BufferedSerial pc; |
JonFreeman | 3:43cb067ecd00 | 6 | extern ee_settings_2020 user_settings; |
JonFreeman | 3:43cb067ecd00 | 7 | |
JonFreeman | 3:43cb067ecd00 | 8 | Engine_Manager::Engine_Manager (PinName ms, PinName cm, PinName for_servo) : MagnetoSignal(ms), CleanedMagneto(cm), Speed_ControlServo(for_servo) |
JonFreeman | 3:43cb067ecd00 | 9 | { |
JonFreeman | 3:43cb067ecd00 | 10 | // Constructor |
JonFreeman | 3:43cb067ecd00 | 11 | magneto_stretch = false; |
JonFreeman | 3:43cb067ecd00 | 12 | rpm_in_run_range = false; |
JonFreeman | 3:43cb067ecd00 | 13 | latest_RPM_reading = core_call_count = 0; |
JonFreeman | 3:43cb067ecd00 | 14 | last_update_time = 0; |
JonFreeman | 3:43cb067ecd00 | 15 | filtered_measured_RPM = requested_RPM = 0.0; |
JonFreeman | 3:43cb067ecd00 | 16 | MagnetoSignal.rise (callback(this, &Engine_Manager::MagRise)); // Attach handler to the rising interruptIn edge |
JonFreeman | 3:43cb067ecd00 | 17 | MagnetoSignal.fall (callback(this, &Engine_Manager::MagFall)); // Attach handler to the rising interruptIn edge |
JonFreeman | 3:43cb067ecd00 | 18 | // microsecs.reset () ; // timer = 0 |
JonFreeman | 3:43cb067ecd00 | 19 | // microsecs.start () ; // 64 bit, counts micro seconds and times out in half million years |
JonFreeman | 3:43cb067ecd00 | 20 | servo_position = MIN_WORKING_THROTTLE; |
JonFreeman | 5:6ca3e7ffc553 | 21 | Set_Speed_Lever (MIN_WORKING_THROTTLE); |
JonFreeman | 3:43cb067ecd00 | 22 | }; |
JonFreeman | 3:43cb067ecd00 | 23 | |
JonFreeman | 3:43cb067ecd00 | 24 | void Engine_Manager::magneto_timeoutC () |
JonFreeman | 3:43cb067ecd00 | 25 | { |
JonFreeman | 3:43cb067ecd00 | 26 | magneto_stretch = false; // Magneto ringing finished by now, re-enable magneto pulse count |
JonFreeman | 3:43cb067ecd00 | 27 | CleanedMagneto = 0; // Cleaned tacho output brought out to pin to look at with scope |
JonFreeman | 3:43cb067ecd00 | 28 | } |
JonFreeman | 3:43cb067ecd00 | 29 | |
JonFreeman | 3:43cb067ecd00 | 30 | void Engine_Manager::MagRise () |
JonFreeman | 3:43cb067ecd00 | 31 | { |
JonFreeman | 3:43cb067ecd00 | 32 | if (!magneto_stretch) { // May get this interrupt more than once per magneto pulse, respond to first, lock out subsequent |
JonFreeman | 3:43cb067ecd00 | 33 | // until magneto_timeout time has elapsed |
JonFreeman | 3:43cb067ecd00 | 34 | magneto_stretch = true; // Set flag preventing repeat interrupts until after Timeout interrupt handler clears it 5ms later |
JonFreeman | 3:43cb067ecd00 | 35 | new_time = microsecs.read_high_resolution_us(); |
JonFreeman | 3:43cb067ecd00 | 36 | magt0 = new_time - magt1; // microsecs between most recent two sparks |
JonFreeman | 3:43cb067ecd00 | 37 | magt1 = new_time; // actual time microsecs of most recent spark |
JonFreeman | 3:43cb067ecd00 | 38 | magneto_timoC.attach_us (callback(this, &Engine_Manager::magneto_timeoutC), DEBOUNCE_US); // To ignore ringing and multiple counts on magneto output, all settled within about 5ms |
JonFreeman | 3:43cb067ecd00 | 39 | CleanedMagneto = 1; // Cleaned tacho output brought out to pin to look at with scope |
JonFreeman | 3:43cb067ecd00 | 40 | } |
JonFreeman | 3:43cb067ecd00 | 41 | } |
JonFreeman | 3:43cb067ecd00 | 42 | |
JonFreeman | 3:43cb067ecd00 | 43 | void Engine_Manager::MagFall () // Need only rising or falling edge, not both |
JonFreeman | 3:43cb067ecd00 | 44 | { |
JonFreeman | 3:43cb067ecd00 | 45 | } |
JonFreeman | 3:43cb067ecd00 | 46 | |
JonFreeman | 3:43cb067ecd00 | 47 | void Engine_Manager::RPM_update () |
JonFreeman | 3:43cb067ecd00 | 48 | { |
JonFreeman | 3:43cb067ecd00 | 49 | last_update_time = microsecs.read_high_resolution_us(); |
JonFreeman | 3:43cb067ecd00 | 50 | time_since_last_spark = last_update_time - magt1; |
JonFreeman | 3:43cb067ecd00 | 51 | if (time_since_last_spark > 500000) { // engine probably not running |
JonFreeman | 3:43cb067ecd00 | 52 | latest_RPM_reading = 0; |
JonFreeman | 3:43cb067ecd00 | 53 | running_flag = false; |
JonFreeman | 3:43cb067ecd00 | 54 | } |
JonFreeman | 3:43cb067ecd00 | 55 | else { |
JonFreeman | 3:43cb067ecd00 | 56 | running_flag = true; |
JonFreeman | 3:43cb067ecd00 | 57 | latest_RPM_reading = (60000000 / magt0); |
JonFreeman | 3:43cb067ecd00 | 58 | } |
JonFreeman | 3:43cb067ecd00 | 59 | filtered_measured_RPM *= (1.0 - RPM_FILTER_FACTOR); |
JonFreeman | 3:43cb067ecd00 | 60 | filtered_measured_RPM += RPM_FILTER_FACTOR * (double)latest_RPM_reading; |
JonFreeman | 3:43cb067ecd00 | 61 | } |
JonFreeman | 3:43cb067ecd00 | 62 | |
JonFreeman | 3:43cb067ecd00 | 63 | bool Engine_Manager::running () // Answers question is engine running? |
JonFreeman | 3:43cb067ecd00 | 64 | { |
JonFreeman | 3:43cb067ecd00 | 65 | return running_flag; |
JonFreeman | 3:43cb067ecd00 | 66 | } |
JonFreeman | 3:43cb067ecd00 | 67 | |
JonFreeman | 3:43cb067ecd00 | 68 | uint32_t Engine_Manager::RPM_latest () |
JonFreeman | 3:43cb067ecd00 | 69 | { |
JonFreeman | 3:43cb067ecd00 | 70 | return latest_RPM_reading; |
JonFreeman | 3:43cb067ecd00 | 71 | } |
JonFreeman | 3:43cb067ecd00 | 72 | |
JonFreeman | 3:43cb067ecd00 | 73 | uint32_t Engine_Manager::RPM_filtered () |
JonFreeman | 3:43cb067ecd00 | 74 | { |
JonFreeman | 3:43cb067ecd00 | 75 | return (uint32_t)filtered_measured_RPM; |
JonFreeman | 3:43cb067ecd00 | 76 | } |
JonFreeman | 3:43cb067ecd00 | 77 | |
JonFreeman | 5:6ca3e7ffc553 | 78 | void Engine_Manager::Set_Speed_Lever (double th) // Make this private once all throttle refs local |
JonFreeman | 3:43cb067ecd00 | 79 | { |
JonFreeman | 3:43cb067ecd00 | 80 | if (user_settings.rd(SERVO_DIR) == 0) |
JonFreeman | 3:43cb067ecd00 | 81 | Speed_ControlServo = th; |
JonFreeman | 3:43cb067ecd00 | 82 | else |
JonFreeman | 3:43cb067ecd00 | 83 | Speed_ControlServo = 1.0 - th; |
JonFreeman | 3:43cb067ecd00 | 84 | } |
JonFreeman | 3:43cb067ecd00 | 85 | |
JonFreeman | 3:43cb067ecd00 | 86 | uint32_t Engine_Manager::set_RPM_literal (uint32_t new_rpm) // Returns latest measured RPM |
JonFreeman | 3:43cb067ecd00 | 87 | { |
JonFreeman | 3:43cb067ecd00 | 88 | if (new_rpm > MAX_RPM) |
JonFreeman | 3:43cb067ecd00 | 89 | new_rpm = MAX_RPM; |
JonFreeman | 3:43cb067ecd00 | 90 | if (new_rpm >= MIN_RPM) { // Runnable or make runnable |
JonFreeman | 3:43cb067ecd00 | 91 | if (!rpm_in_run_range) { // Transition from idle to working revs |
JonFreeman | 3:43cb067ecd00 | 92 | rpm_in_run_range = true; |
JonFreeman | 3:43cb067ecd00 | 93 | servo_position = MIN_WORKING_THROTTLE; // set throttle to probably somewhere near low useful revs |
JonFreeman | 5:6ca3e7ffc553 | 94 | Set_Speed_Lever (MIN_WORKING_THROTTLE); |
JonFreeman | 3:43cb067ecd00 | 95 | } // Endof transition from idle to working revs |
JonFreeman | 3:43cb067ecd00 | 96 | } // Endof Runnable or make runnable |
JonFreeman | 3:43cb067ecd00 | 97 | else { // requested rpm are below useful, so kill engine |
JonFreeman | 3:43cb067ecd00 | 98 | if (rpm_in_run_range) { // detect transition from useful work to idle |
JonFreeman | 3:43cb067ecd00 | 99 | servo_position = 0.0; |
JonFreeman | 5:6ca3e7ffc553 | 100 | Set_Speed_Lever (0.0); |
JonFreeman | 3:43cb067ecd00 | 101 | rpm_in_run_range = false; |
JonFreeman | 3:43cb067ecd00 | 102 | } |
JonFreeman | 3:43cb067ecd00 | 103 | } |
JonFreeman | 3:43cb067ecd00 | 104 | requested_RPM = (double) new_rpm; |
JonFreeman | 3:43cb067ecd00 | 105 | return latest_RPM_reading; |
JonFreeman | 3:43cb067ecd00 | 106 | } |
JonFreeman | 3:43cb067ecd00 | 107 | |
JonFreeman | 3:43cb067ecd00 | 108 | uint32_t Engine_Manager::set_RPM_percent (uint32_t new_percent) |
JonFreeman | 3:43cb067ecd00 | 109 | { // Returns latest measured RPM |
JonFreeman | 3:43cb067ecd00 | 110 | uint32_t temp = 0; |
JonFreeman | 3:43cb067ecd00 | 111 | if (new_percent > 100) new_percent = 100; |
JonFreeman | 3:43cb067ecd00 | 112 | if (new_percent != 0) |
JonFreeman | 3:43cb067ecd00 | 113 | temp = MIN_RPM + (((MAX_RPM - MIN_RPM) * new_percent) / 100); |
JonFreeman | 3:43cb067ecd00 | 114 | return set_RPM_literal (temp); |
JonFreeman | 3:43cb067ecd00 | 115 | } |
JonFreeman | 3:43cb067ecd00 | 116 | |
JonFreeman | 3:43cb067ecd00 | 117 | uint32_t Engine_Manager::RPM_percent_to_actual (uint32_t new_percent) |
JonFreeman | 3:43cb067ecd00 | 118 | { // Returns RPM percentage converted to actual RPM |
JonFreeman | 3:43cb067ecd00 | 119 | uint32_t temp = 0; |
JonFreeman | 3:43cb067ecd00 | 120 | if (new_percent > 100) new_percent = 100; |
JonFreeman | 3:43cb067ecd00 | 121 | if (new_percent != 0) |
JonFreeman | 3:43cb067ecd00 | 122 | temp = MIN_RPM + (((MAX_RPM - MIN_RPM) * new_percent) / 100); |
JonFreeman | 3:43cb067ecd00 | 123 | return temp; |
JonFreeman | 3:43cb067ecd00 | 124 | } |
JonFreeman | 3:43cb067ecd00 | 125 | |
JonFreeman | 3:43cb067ecd00 | 126 | uint32_t Engine_Manager::get_RPM_requested () // Returns latest requested RPM |
JonFreeman | 3:43cb067ecd00 | 127 | { |
JonFreeman | 3:43cb067ecd00 | 128 | return (uint32_t) requested_RPM; |
JonFreeman | 3:43cb067ecd00 | 129 | } |
JonFreeman | 3:43cb067ecd00 | 130 | |
JonFreeman | 3:43cb067ecd00 | 131 | double Engine_Manager::get_servo_position () // Only call from test printf in main |
JonFreeman | 3:43cb067ecd00 | 132 | { |
JonFreeman | 3:43cb067ecd00 | 133 | return servo_position; |
JonFreeman | 3:43cb067ecd00 | 134 | } |
JonFreeman | 3:43cb067ecd00 | 135 | |
JonFreeman | 3:43cb067ecd00 | 136 | void Engine_Manager::manager_core() // Call this at 100 Hz |
JonFreeman | 3:43cb067ecd00 | 137 | { |
JonFreeman | 3:43cb067ecd00 | 138 | uint32_t i = ++core_call_count; |
JonFreeman | 3:43cb067ecd00 | 139 | int32_t rpm_error; // signed |
JonFreeman | 3:43cb067ecd00 | 140 | RPM_update (); // Updates filtered and unfiltered RPM measured, 100 times per sec |
JonFreeman | 3:43cb067ecd00 | 141 | i &= 7; |
JonFreeman | 3:43cb067ecd00 | 142 | switch (i) { |
JonFreeman | 3:43cb067ecd00 | 143 | case 1: // Doing this at 12.5 Hz |
JonFreeman | 3:43cb067ecd00 | 144 | rpm_error = filtered_measured_RPM - requested_RPM; // all doubles. Speed error is positive |
JonFreeman | 3:43cb067ecd00 | 145 | if (rpm_in_run_range && (abs(rpm_error) > RPM_DEADBAND)) { // Try to adjust speed if not within margin |
JonFreeman | 3:43cb067ecd00 | 146 | servo_position -= rpm_error / RPM_ERR_DENOM; //Denominator sets gain, the 'P', Proportional as in PID |
JonFreeman | 3:43cb067ecd00 | 147 | if (servo_position < MIN_WORKING_THROTTLE) // Keep servo_position within limits |
JonFreeman | 3:43cb067ecd00 | 148 | servo_position = MIN_WORKING_THROTTLE; |
JonFreeman | 3:43cb067ecd00 | 149 | if (servo_position > MAX_WORKING_THROTTLE) |
JonFreeman | 3:43cb067ecd00 | 150 | servo_position = MAX_WORKING_THROTTLE; |
JonFreeman | 5:6ca3e7ffc553 | 151 | Set_Speed_Lever (servo_position); // Update signal to engine throttle control servo |
JonFreeman | 3:43cb067ecd00 | 152 | } |
JonFreeman | 3:43cb067ecd00 | 153 | break; |
JonFreeman | 3:43cb067ecd00 | 154 | default: |
JonFreeman | 3:43cb067ecd00 | 155 | break; |
JonFreeman | 3:43cb067ecd00 | 156 | } |
JonFreeman | 3:43cb067ecd00 | 157 | } |
JonFreeman | 3:43cb067ecd00 | 158 | |
JonFreeman | 3:43cb067ecd00 | 159 | |
JonFreeman | 3:43cb067ecd00 | 160 | |
JonFreeman | 3:43cb067ecd00 | 161 |