forked from speeduino/speeduino
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathspeeduino.ino
More file actions
1196 lines (1050 loc) · 58.6 KB
/
Copy pathspeeduino.ino
File metadata and controls
1196 lines (1050 loc) · 58.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Speeduino - Simple engine management for the Arduino Mega 2560 platform
Copyright (C) Josh Stewart
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//**************************************************************************************************
// Config section
#define engineSquirtsPerCycle 2 //Would be 1 for a 2 stroke
//**************************************************************************************************
#include "globals.h"
#include "utils.h"
#include "table.h"
#include "scheduler.h"
#include "comms.h"
#include "math.h"
#include "corrections.h"
#include "timers.h"
#include "display.h"
#include "decoders.h"
#include "idle.h"
#include "auxiliaries.h"
#include "fastAnalog.h"
#include "libs/PID_v1/PID_v1.h"
#ifdef __SAM3X8E__
//Do stuff for ARM based CPUs
#else
#include "storage.h"
#endif
struct config1 configPage1;
struct config2 configPage2;
struct config3 configPage3;
struct config4 configPage4;
int req_fuel_uS, inj_opentime_uS;
#define MAX_RPM 15000 //This is the maximum rpm that the ECU will attempt to run at. It is NOT related to the rev limiter, but is instead dictates how fast certain operations will be allowed to run. Lower number gives better performance
volatile byte startRevolutions = 0; //A counter for how many revolutions have been completed since sync was achieved.
volatile byte ign1LastRev;
volatile byte ign2LastRev;
volatile byte ign3LastRev;
volatile byte ign4LastRev;
bool ignitionOn = false; //The current state of the ignition system
bool fuelOn = false; //The current state of the ignition system
bool fuelPumpOn = false; //The current status of the fuel pump
void (*trigger)(); //Pointer for the trigger function (Gets pointed to the relevant decoder)
void (*triggerSecondary)(); //Pointer for the secondary trigger function (Gets pointed to the relevant decoder)
int (*getRPM)(); //Pointer to the getRPM function (Gets pointed to the relevant decoder)
int (*getCrankAngle)(int); //Pointer to the getCrank Angle function (Gets pointed to the relevant decoder)
struct table3D fuelTable; //16x16 fuel map
struct table3D ignitionTable; //16x16 ignition map
struct table3D afrTable; //16x16 afr target map
struct table3D boostTable; //8x8 boost map
struct table3D vvtTable; //8x8 vvt map
struct table2D taeTable; //4 bin TPS Acceleration Enrichment map (2D)
struct table2D WUETable; //10 bin Warm Up Enrichment map (2D)
struct table2D dwellVCorrectionTable; //6 bin dwell voltage correction (2D)
struct table2D injectorVCorrectionTable; //6 bin injector voltage correction (2D)
struct table2D IATDensityCorrectionTable; //9 bin inlet air temperature density correction (2D)
struct table2D IATRetardTable; //6 bin ignition adjustment based on inlet air temperature (2D)
byte cltCalibrationTable[CALIBRATION_TABLE_SIZE];
byte iatCalibrationTable[CALIBRATION_TABLE_SIZE];
byte o2CalibrationTable[CALIBRATION_TABLE_SIZE];
//These variables are used for tracking the number of running sensors values that appear to be errors. Once a threshold is reached, the sensor reading will go to default value and assume the sensor is faulty
byte mapErrorCount = 0;
byte iatErrorCount = 0;
byte cltErrorCount = 0;
unsigned long counter;
unsigned long currentLoopTime; //The time the current loop started (uS)
unsigned long previousLoopTime; //The time the previous loop started (uS)
unsigned long MAPrunningValue; //Used for tracking either the total of all MAP readings in this cycle (Event average) or the lowest value detected in this cycle (event minimum)
unsigned int MAPcount; //Number of samples taken in the current MAP cycle
byte MAPcurRev = 0; //Tracks which revolution we're sampling on
byte coilHIGH = HIGH;
byte coilLOW = LOW;
byte fanHIGH = HIGH; // Used to invert the cooling fan output
byte fanLOW = LOW; // Used to invert the cooling fan output
struct statuses currentStatus;
volatile int mainLoopCount;
byte deltaToothCount = 0; //The last tooth that was used with the deltaV calc
int rpmDelta;
byte ignitionCount;
unsigned long secCounter; //The next time to incremen 'runSecs' counter.
int channel1IgnDegrees; //The number of crank degrees until cylinder 1 is at TDC (This is obviously 0 for virtually ALL engines, but there's some weird ones)
int channel2IgnDegrees; //The number of crank degrees until cylinder 2 (and 5/6/7/8) is at TDC
int channel3IgnDegrees; //The number of crank degrees until cylinder 3 (and 5/6/7/8) is at TDC
int channel4IgnDegrees; //The number of crank degrees until cylinder 4 (and 5/6/7/8) is at TDC
int channel1InjDegrees; //The number of crank degrees until cylinder 1 is at TDC (This is obviously 0 for virtually ALL engines, but there's some weird ones)
int channel2InjDegrees; //The number of crank degrees until cylinder 2 (and 5/6/7/8) is at TDC
int channel3InjDegrees; //The number of crank degrees until cylinder 3 (and 5/6/7/8) is at TDC
int channel4InjDegrees; //The number of crank degrees until cylinder 4 (and 5/6/7/8) is at TDC
//These are the functions the get called to begin and end the ignition coil charging. They are required for the various spark output modes
void (*ign1StartFunction)();
void (*ign1EndFunction)();
void (*ign2StartFunction)();
void (*ign2EndFunction)();
void (*ign3StartFunction)();
void (*ign3EndFunction)();
void (*ign4StartFunction)();
void (*ign4EndFunction)();
int timePerDegree;
byte degreesPerLoop; //The number of crank degrees that pass for each mainloop of the program
volatile bool fpPrimed = false; //Tracks whether or not the fuel pump priming has been completed yet
void setup()
{
//Setup the dummy fuel and ignition tables
//dummyFuelTable(&fuelTable);
//dummyIgnitionTable(&ignitionTable);
table3D_setSize(&fuelTable, 16);
table3D_setSize(&ignitionTable, 16);
table3D_setSize(&afrTable, 16);
table3D_setSize(&boostTable, 8);
table3D_setSize(&vvtTable, 8);
loadConfig();
//Repoint the 2D table structs to the config pages that were just loaded
taeTable.valueSize = SIZE_BYTE; //Set this table to use byte values
taeTable.xSize = 4;
taeTable.values = configPage2.taeValues;
taeTable.axisX = configPage2.taeBins;
WUETable.valueSize = SIZE_BYTE; //Set this table to use byte values
WUETable.xSize = 10;
WUETable.values = configPage1.wueValues;
WUETable.axisX = configPage2.wueBins;
//The WUE X axis values are hard coded (Don't ask, they just are)
WUETable.axisX[0] = 0;
WUETable.axisX[1] = 11;
WUETable.axisX[2] = 22;
WUETable.axisX[3] = 33;
WUETable.axisX[4] = 44;
WUETable.axisX[5] = 56;
WUETable.axisX[6] = 67;
WUETable.axisX[7] = 78;
WUETable.axisX[8] = 94;
WUETable.axisX[9] = 111;
dwellVCorrectionTable.valueSize = SIZE_BYTE;
dwellVCorrectionTable.xSize = 6;
dwellVCorrectionTable.values = configPage2.dwellCorrectionValues;
dwellVCorrectionTable.axisX = configPage3.voltageCorrectionBins;
injectorVCorrectionTable.valueSize = SIZE_BYTE;
injectorVCorrectionTable.xSize = 6;
injectorVCorrectionTable.values = configPage3.injVoltageCorrectionValues;
injectorVCorrectionTable.axisX = configPage3.voltageCorrectionBins;
IATDensityCorrectionTable.valueSize = SIZE_BYTE;
IATDensityCorrectionTable.xSize = 9;
IATDensityCorrectionTable.values = configPage3.airDenRates;
IATDensityCorrectionTable.axisX = configPage3.airDenBins;
IATRetardTable.valueSize = SIZE_BYTE;
IATRetardTable.xSize = 6;
IATRetardTable.values = configPage2.iatRetValues;
IATRetardTable.axisX = configPage2.iatRetBins;
//Setup the calibration tables
loadCalibration();
//Set the pin mappings
setPinMapping(configPage1.pinMapping);
//Need to check early on whether the coil charging is inverted. If this is not set straight away it can cause an unwanted spark at bootup
if(configPage2.IgInv == 1) { coilHIGH = LOW, coilLOW = HIGH; }
else { coilHIGH = HIGH, coilLOW = LOW; }
digitalWrite(pinCoil1, coilLOW);
digitalWrite(pinCoil2, coilLOW);
digitalWrite(pinCoil3, coilLOW);
digitalWrite(pinCoil4, coilLOW);
//Similar for injectors, make sure they're turned off
digitalWrite(pinInjector1, LOW);
digitalWrite(pinInjector2, LOW);
digitalWrite(pinInjector3, LOW);
digitalWrite(pinInjector4, LOW);
//Set the tacho output default state
digitalWrite(pinTachOut, HIGH);
initialiseSchedulers();
initialiseTimers();
initialiseDisplay();
initialiseIdle();
initialiseFan();
initialiseAuxPWM();
//Once the configs have been loaded, a number of one time calculations can be completed
req_fuel_uS = configPage1.reqFuel * 100; //Convert to uS and an int. This is the only variable to be used in calculations
inj_opentime_uS = configPage1.injOpen * 100; //Injector open time. Comes through as ms*10 (Eg 15.5ms = 155).
//Begin the main crank trigger interrupt pin setup
//The interrupt numbering is a bit odd - See here for reference: http://arduino.cc/en/Reference/AttachInterrupt
//These assignments are based on the Arduino Mega AND VARY BETWEEN BOARDS. Please confirm the board you are using and update acordingly.
byte triggerInterrupt = 0; // By default, use the first interrupt
byte triggerInterrupt2 = 1;
currentStatus.RPM = 0;
currentStatus.hasSync = false;
currentStatus.runSecs = 0;
currentStatus.secl = 0;
triggerFilterTime = 0; //Trigger filter time is the shortest possible time (in uS) that there can be between crank teeth (ie at max RPM). Any pulses that occur faster than this time will be disgarded as noise. This is simply a default value, the actual values are set in the setup() functinos of each decoder
switch (pinTrigger) {
//Arduino Mega 2560 mapping
case 2:
triggerInterrupt = 0; break;
case 3:
triggerInterrupt = 1; break;
case 18:
triggerInterrupt = 5; break;
case 19:
triggerInterrupt = 4; break;
case 20:
triggerInterrupt = 3; break;
case 21:
triggerInterrupt = 2; break;
}
switch (pinTrigger2) {
//Arduino Mega 2560 mapping
case 2:
triggerInterrupt2 = 0; break;
case 3:
triggerInterrupt2 = 1; break;
case 18:
triggerInterrupt2 = 5; break;
case 19:
triggerInterrupt2 = 4; break;
case 20:
triggerInterrupt2 = 3; break;
case 21:
triggerInterrupt2 = 2; break;
}
pinMode(pinTrigger, INPUT);
pinMode(pinTrigger2, INPUT);
pinMode(pinTrigger3, INPUT);
//digitalWrite(pinTrigger, HIGH);
//Set the trigger function based on the decoder in the config
switch (configPage2.TrigPattern)
{
case 0:
//Missing tooth decoder
triggerSetup_missingTooth();
trigger = triggerPri_missingTooth;
triggerSecondary = triggerSec_missingTooth;
getRPM = getRPM_missingTooth;
getCrankAngle = getCrankAngle_missingTooth;
if(configPage2.TrigEdge == 0) { attachInterrupt(triggerInterrupt, trigger, RISING); } // Attach the crank trigger wheel interrupt (Hall sensor drags to ground when triggering)
else { attachInterrupt(triggerInterrupt, trigger, FALLING); }
break;
case 1:
// Basic distributor
triggerSetup_BasicDistributor();
trigger = triggerPri_BasicDistributor;
getRPM = getRPM_BasicDistributor;
getCrankAngle = getCrankAngle_BasicDistributor;
if(configPage2.TrigEdge == 0) { attachInterrupt(triggerInterrupt, trigger, RISING); } // Attach the crank trigger wheel interrupt (Hall sensor drags to ground when triggering)
else { attachInterrupt(triggerInterrupt, trigger, FALLING); }
break;
case 2:
triggerSetup_DualWheel();
trigger = triggerPri_DualWheel;
getRPM = getRPM_DualWheel;
getCrankAngle = getCrankAngle_DualWheel;
if(configPage2.TrigEdge == 0) { attachInterrupt(triggerInterrupt, trigger, RISING); } // Attach the crank trigger wheel interrupt (Hall sensor drags to ground when triggering)
else { attachInterrupt(triggerInterrupt, trigger, FALLING); }
attachInterrupt(triggerInterrupt2, triggerSec_DualWheel, RISING);
break;
case 3:
triggerSetup_GM7X();
trigger = triggerPri_GM7X;
getRPM = getRPM_GM7X;
getCrankAngle = getCrankAngle_GM7X;
if(configPage2.TrigEdge == 0) { attachInterrupt(triggerInterrupt, trigger, RISING); } // Attach the crank trigger wheel interrupt (Hall sensor drags to ground when triggering)
else { attachInterrupt(triggerInterrupt, trigger, FALLING); }
break;
case 4:
triggerSetup_4G63();
trigger = triggerPri_4G63;
getRPM = getRPM_4G63;
getCrankAngle = getCrankAngle_4G63;
//These may both need to change, not sure
if(configPage2.TrigEdge == 0)
{
attachInterrupt(triggerInterrupt, trigger, CHANGE); // Attach the crank trigger wheel interrupt (Hall sensor drags to ground when triggering)
attachInterrupt(triggerInterrupt2, triggerSec_4G63, FALLING); //changed
}
else
{
attachInterrupt(triggerInterrupt, trigger, CHANGE); // Primary trigger connects to
attachInterrupt(triggerInterrupt2, triggerSec_4G63, FALLING);
}
break;
case 5:
triggerSetup_24X();
trigger = triggerPri_24X;
getRPM = getRPM_24X;
getCrankAngle = getCrankAngle_24X;
if(configPage2.TrigEdge == 0) { attachInterrupt(triggerInterrupt, trigger, RISING); } // Attach the crank trigger wheel interrupt (Hall sensor drags to ground when triggering)
else { attachInterrupt(triggerInterrupt, trigger, FALLING); } // Primary trigger connects to
attachInterrupt(triggerInterrupt2, triggerSec_24X, CHANGE);
break;
case 6:
triggerSetup_Jeep2000();
trigger = triggerPri_Jeep2000;
getRPM = getRPM_Jeep2000;
getCrankAngle = getCrankAngle_Jeep2000;
if(configPage2.TrigEdge == 0) { attachInterrupt(triggerInterrupt, trigger, RISING); } // Attach the crank trigger wheel interrupt (Hall sensor drags to ground when triggering)
else { attachInterrupt(triggerInterrupt, trigger, FALLING); } // Primary trigger connects to
attachInterrupt(triggerInterrupt2, triggerSec_Jeep2000, CHANGE);
break;
case 7:
triggerSetup_Audi135();
trigger = triggerPri_Audi135;
getRPM = getRPM_Audi135;
getCrankAngle = getCrankAngle_Audi135;
if(configPage2.TrigEdge == 0) { attachInterrupt(triggerInterrupt, trigger, RISING); } // Attach the crank trigger wheel interrupt (Hall sensor drags to ground when triggering)
else { attachInterrupt(triggerInterrupt, trigger, FALLING); }
attachInterrupt(triggerInterrupt2, triggerSec_Audi135, RISING);
break;
default:
trigger = triggerPri_missingTooth;
getRPM = getRPM_missingTooth;
getCrankAngle = getCrankAngle_missingTooth;
if(configPage2.TrigEdge == 0) { attachInterrupt(triggerInterrupt, trigger, RISING); } // Attach the crank trigger wheel interrupt (Hall sensor drags to ground when triggering)
else { attachInterrupt(triggerInterrupt, trigger, FALLING); }
break;
}
//End crank triger interrupt attachment
req_fuel_uS = req_fuel_uS / engineSquirtsPerCycle; //The req_fuel calculation above gives the total required fuel (At VE 100%) in the full cycle. If we're doing more than 1 squirt per cycle then we need to split the amount accordingly. (Note that in a non-sequential 4-stroke setup you cannot have less than 2 squirts as you cannot determine the stroke to make the single squirt on)
//Initial values for loop times
previousLoopTime = 0;
currentLoopTime = micros();
Serial.begin(115200);
//This sets the ADC (Analog to Digitial Converter) to run at 1Mhz, greatly reducing analog read times (MAP/TPS)
//1Mhz is the fastest speed permitted by the CPU without affecting accuracy
//Please see chapter 11 of 'Practical Arduino' (http://books.google.com.au/books?id=HsTxON1L6D4C&printsec=frontcover#v=onepage&q&f=false) for more details
//Can be disabled by removing the #include "fastAnalog.h" above
#ifdef sbi
sbi(ADCSRA,ADPS2);
cbi(ADCSRA,ADPS1);
cbi(ADCSRA,ADPS0);
#endif
mainLoopCount = 0;
ignitionCount = 0;
//Calculate the number of degrees between cylinders
switch (configPage1.nCylinders) {
case 1:
channel1IgnDegrees = 0;
channel1InjDegrees = 0;
break;
case 2:
channel1IgnDegrees = 0;
channel2IgnDegrees = 180;
//For alternatiing injection, the squirt occurs at different times for each channel
if(configPage1.alternate)
{
channel1InjDegrees = 0;
channel2InjDegrees = 180;
}
else { channel1InjDegrees = channel2InjDegrees = 0; } //For simultaneous, all squirts happen at the same time
break;
case 3:
channel1IgnDegrees = 0;
channel2IgnDegrees = 120;
channel3IgnDegrees = 240;
//For alternatiing injection, the squirt occurs at different times for each channel
if(configPage1.alternate)
{
channel1InjDegrees = 0;
channel2InjDegrees = 120;
channel3InjDegrees = 240;
}
else { channel1InjDegrees = channel2InjDegrees = channel3InjDegrees = 0; } //For simultaneous, all squirts happen at the same time
break;
case 4:
channel1IgnDegrees = 0;
channel2IgnDegrees = 180;
//For alternatiing injection, the squirt occurs at different times for each channel
if(configPage1.alternate)
{
channel1InjDegrees = 0;
channel2InjDegrees = 180;
}
else { channel1InjDegrees = channel2InjDegrees = 0; } //For simultaneous, all squirts happen at the same time
break;
case 6:
channel1IgnDegrees = 0;
channel2IgnDegrees = 120;
channel3IgnDegrees = 240;
//For alternatiing injection, the squirt occurs at different times for each channel
if(configPage1.alternate)
{
channel1InjDegrees = 0;
channel2InjDegrees = 120;
channel3InjDegrees = 240;
}
else { channel1InjDegrees = channel2InjDegrees = channel3InjDegrees = 0; } //For simultaneous, all squirts happen at the same time
configPage1.injLayout = 0; //This is a failsafe. We can never run semi-sequential with more than 4 cylinders
break;
case 8:
channel1IgnDegrees = 0;
channel2IgnDegrees = 90;
channel3IgnDegrees = 180;
channel4IgnDegrees = 270;
//For alternatiing injection, the squirt occurs at different times for each channel
if(configPage1.alternate)
{
channel1InjDegrees = 0;
channel2InjDegrees = 90;
channel3InjDegrees = 180;
channel4InjDegrees = 270;
}
else { channel1InjDegrees = channel2InjDegrees = channel3InjDegrees = channel4InjDegrees = 0; } //For simultaneous, all squirts happen at the same time
configPage1.injLayout = 0; //This is a failsafe. We can never run semi-sequential with more than 4 cylinders
break;
default: //Handle this better!!!
channel1InjDegrees = 0;
channel2InjDegrees = 180;
break;
}
switch(configPage2.sparkMode)
{
case 0:
//Wasted Spark (Normal mode)
ign1StartFunction = beginCoil1Charge;
ign1EndFunction = endCoil1Charge;
ign2StartFunction = beginCoil2Charge;
ign2EndFunction = endCoil2Charge;
ign3StartFunction = beginCoil3Charge;
ign3EndFunction = endCoil3Charge;
ign4StartFunction = beginCoil4Charge;
ign4EndFunction = endCoil4Charge;
break;
case 1:
//Single channel mode. All ignition pulses are on channel 1
ign1StartFunction = beginCoil1Charge;
ign1EndFunction = endCoil1Charge;
ign2StartFunction = beginCoil1Charge;
ign2EndFunction = endCoil1Charge;
ign3StartFunction = beginCoil1Charge;
ign3EndFunction = endCoil1Charge;
ign4StartFunction = beginCoil1Charge;
ign4EndFunction = endCoil1Charge;
break;
case 2:
//Wasted COP mode. Ignition channels 1&3 and 2&4 are paired together
//This is not a valid mode for >4 cylinders
if( configPage1.nCylinders <= 4 )
{
ign1StartFunction = beginCoil1and3Charge;
ign1EndFunction = endCoil1and3Charge;
ign2StartFunction = beginCoil2and4Charge;
ign2EndFunction = endCoil2and4Charge;
ign3StartFunction = nullCallback;
ign3EndFunction = nullCallback;
ign4StartFunction = nullCallback;
ign4EndFunction = nullCallback;
}
else
{
//If the person has inadvertantly selected this when running more than 4 cylinders, just use standard Wasted spark mode
ign1StartFunction = beginCoil1Charge;
ign1EndFunction = endCoil1Charge;
ign2StartFunction = beginCoil2Charge;
ign2EndFunction = endCoil2Charge;
ign3StartFunction = beginCoil3Charge;
ign3EndFunction = endCoil3Charge;
ign4StartFunction = beginCoil4Charge;
ign4EndFunction = endCoil4Charge;
}
break;
default:
//Wasted spark (Shouldn't ever happen anyway)
ign1StartFunction = beginCoil1Charge;
ign1EndFunction = endCoil1Charge;
ign2StartFunction = beginCoil2Charge;
ign2EndFunction = endCoil2Charge;
ign3StartFunction = beginCoil3Charge;
ign3EndFunction = endCoil3Charge;
ign4StartFunction = beginCoil4Charge;
ign4EndFunction = endCoil4Charge;
break;
}
//Begin priming the fuel pump. This is turned off in the low resolution, 1s interrupt in timers.ino
digitalWrite(pinFuelPump, HIGH);
fuelPumpOn = true;
//Perform the priming pulses. Set these to run at an arbitrary time in the future (100us). The prime pulse value is in ms*10, so need to multiple by 100 to get to uS
setFuelSchedule1(openInjector1and4, 100, (unsigned long)(configPage1.primePulse * 100), closeInjector1and4);
setFuelSchedule2(openInjector2and3, 100, (unsigned long)(configPage1.primePulse * 100), closeInjector2and3);
}
void loop()
{
mainLoopCount++;
//Check for any requets from serial. Serial operations are checked under 2 scenarios:
// 1) Every 64 loops (64 Is more than fast enough for TunerStudio). This function is equivalent to ((loopCount % 64) == 1) but is considerably faster due to not using the mod or division operations
// 2) If the amount of data in the serial buffer is greater than a set threhold (See globals.h). This is to avoid serial buffer overflow when large amounts of data is being sent
if ( ((mainLoopCount & 63) == 1) or (Serial.available() > SERIAL_BUFFER_THRESHOLD) )
{
if (Serial.available() > 0)
{
command();
}
}
// if (configPage1.displayType && (mainLoopCount & 255) == 1) { updateDisplay();} //Displays currently disabled
previousLoopTime = currentLoopTime;
currentLoopTime = micros();
unsigned long timeToLastTooth = (currentLoopTime - toothLastToothTime);
if ( (timeToLastTooth < 500000L) || (toothLastToothTime > currentLoopTime) ) //Check how long ago the last tooth was seen compared to now. If it was more than half a second ago then the engine is probably stopped. toothLastToothTime can be greater than currentLoopTime if a pulse occurs between getting the lastest time and doing the comparison
{
int lastRPM = currentStatus.RPM; //Need to record this for rpmDOT calculation
currentStatus.RPM = getRPM();
if(fuelPumpOn == false) { digitalWrite(pinFuelPump, HIGH); fuelPumpOn = true; } //Check if the fuel pump is on and turn it on if it isn't.
currentStatus.rpmDOT = ldiv(1000000, (currentLoopTime - previousLoopTime)).quot * (currentStatus.RPM - lastRPM); //This is the RPM per second that the engine has accelerated/decelleratedin the last loop
}
else
{
//We reach here if the time between teeth is too great. This VERY likely means the engine has stopped
currentStatus.RPM = 0;
currentStatus.PW = 0;
currentStatus.VE = 0;
toothLastToothTime = 0;
currentStatus.hasSync = false;
currentStatus.runSecs = 0; //Reset the counter for number of seconds running.
secCounter = 0; //Reset our seconds counter.
startRevolutions = 0;
MAPcurRev = 0;
currentStatus.rpmDOT = 0;
ignitionOn = false;
fuelOn = false;
if (fpPrimed) { digitalWrite(pinFuelPump, LOW); } //Turn off the fuel pump, but only if the priming is complete
fuelPumpOn = false;
}
//Uncomment the following for testing
/*
currentStatus.hasSync = true;
currentStatus.RPM = 500;
*/
//***SET STATUSES***
//-----------------------------------------------------------------------------------------------------
//MAP Sampling system
int tempReading;
switch(configPage1.mapSample)
{
case 0:
//Instantaneous MAP readings
tempReading = analogRead(pinMAP);
//Error checking
if(tempReading >= VALID_MAP_MAX || tempReading <= VALID_MAP_MIN) { mapErrorCount += 1; }
else { currentStatus.mapADC = tempReading; mapErrorCount = 0; }
currentStatus.MAP = map(currentStatus.mapADC, 0, 1023, configPage1.mapMin, configPage1.mapMax); //Get the current MAP value
break;
case 1:
//Average of a cycle
if( (MAPcurRev == startRevolutions) || (MAPcurRev == startRevolutions+1) ) //2 revolutions are looked at for 4 stroke. 2 stroke not currently catered for.
{
tempReading = analogRead(pinMAP);
//Error check
if(tempReading < VALID_MAP_MAX && tempReading > VALID_MAP_MIN)
{
MAPrunningValue = MAPrunningValue + tempReading; //Add the current reading onto the total
MAPcount++;
}
else { mapErrorCount += 1; }
}
else
{
//Reaching here means that the last cylce has completed and the MAP value should be calculated
currentStatus.mapADC = ldiv(MAPrunningValue, MAPcount).quot;
currentStatus.MAP = map(currentStatus.mapADC, 0, 1023, configPage1.mapMin, configPage1.mapMax); //Get the current MAP value
MAPcurRev = startRevolutions; //Reset the current rev count
MAPrunningValue = 0;
MAPcount = 0;
}
break;
case 2:
//Minimum reading in a cycle
if( (MAPcurRev == startRevolutions) || (MAPcurRev == startRevolutions+1) ) //2 revolutions are looked at for 4 stroke. 2 stroke not currently catered for.
{
tempReading = analogRead(pinMAP);
//Error check
if(tempReading < VALID_MAP_MAX && tempReading > VALID_MAP_MIN)
{
if( tempReading < MAPrunningValue) { MAPrunningValue = tempReading; } //Check whether the current reading is lower than the running minimum
}
else { mapErrorCount += 1; }
}
else
{
//Reaching here means that the last cylce has completed and the MAP value should be calculated
currentStatus.mapADC = MAPrunningValue;
currentStatus.MAP = map(currentStatus.mapADC, 0, 1023, configPage1.mapMin, configPage1.mapMax); //Get the current MAP value
MAPcurRev = startRevolutions; //Reset the current rev count
MAPrunningValue = 1023; //Reset the latest value so the next reading will always be lower
}
break;
}
//TPS setting to be performed every 32 loops (any faster and it can upset the TPSdot sampling time)
if ((mainLoopCount & 31) == 1)
{
currentStatus.TPSlast = currentStatus.TPS;
currentStatus.TPSlast_time = currentStatus.TPS_time;
currentStatus.tpsADC = fastMap1023toX(analogRead(pinTPS), 0, 1023, 0, 255); //Get the current raw TPS ADC value and map it into a byte
//Check that the ADC values fall within the min and max ranges (Should always be the case, but noise can cause these to fluctuate outside the defined range).
if (currentStatus.tpsADC < configPage1.tpsMin) { currentStatus.tpsADC = configPage1.tpsMin; }
else if(currentStatus.tpsADC > configPage1.tpsMax) { currentStatus.tpsADC = configPage1.tpsMax; }
currentStatus.TPS = map(currentStatus.tpsADC, configPage1.tpsMin, configPage1.tpsMax, 0, 100); //Take the raw TPS ADC value and convert it into a TPS% based on the calibrated values
currentStatus.TPS_time = currentLoopTime;
//Check for launch in (clutch) can be done around here too
currentStatus.launching = !digitalRead(pinLaunch);
//And check whether the tooth log buffer is ready
if(toothHistoryIndex > TOOTH_LOG_SIZE) { BIT_SET(currentStatus.squirt, BIT_SQUIRT_TOOTHLOG1READY); }
}
//The IAT and CLT readings can be done less frequently. This still runs about 4 times per second
if ((mainLoopCount & 255) == 1)
{
//currentStatus.cltADC = map(analogRead(pinCLT), 0, 1023, 0, 511); //Get the current raw CLT value
currentStatus.cltADC = fastMap1023toX(analogRead(pinCLT), 0, 1023, 0, 511); //Get the current raw CLT value
currentStatus.iatADC = map(analogRead(pinIAT), 0, 1023, 0, 511); //Get the current raw IAT value
currentStatus.O2ADC = map(analogRead(pinO2), 0, 1023, 0, 511); //Get the current O2 value. Calibration is from AFR values 7.35 to 22.4. This is the correct calibration for an Innovate Wideband 0v - 5V unit. Proper calibration is still a WIP
currentStatus.O2_2ADC = map(analogRead(pinO2_2), 0, 1023, 0, 511); //Get the current O2 value. Calibration is from AFR values 7.35 to 22.4. This is the correct calibration for an Innovate Wideband 0v - 5V unit. Proper calibration is still a WIP
//currentStatus.battery10 = map(analogRead(pinBat), 0, 1023, 0, 245); //Get the current raw Battery value. Permissible values are from 0v to 24.5v (245)
currentStatus.battery10 = fastMap1023toX(analogRead(pinBat), 0, 1023, 0, 245); //Get the current raw Battery value. Permissible values are from 0v to 24.5v (245)
//currentStatus.batADC = map(analogRead(pinBat), 0, 1023, 0, 255); //Get the current raw Battery value
currentStatus.coolant = cltCalibrationTable[currentStatus.cltADC] - CALIBRATION_TEMPERATURE_OFFSET; //Temperature calibration values are stored as positive bytes. We subtract 40 from them to allow for negative temperatures
currentStatus.IAT = iatCalibrationTable[currentStatus.iatADC] - CALIBRATION_TEMPERATURE_OFFSET;
currentStatus.O2 = o2CalibrationTable[currentStatus.O2ADC];
currentStatus.O2_2 = o2CalibrationTable[currentStatus.O2_2ADC];
vvtControl();
boostControl(); //Most boost tends to run at about 30Hz, so placing it here ensures a new target time is fetched frequently enough
}
//Always check for sync
//Main loop runs within this clause
if (currentStatus.hasSync && (currentStatus.RPM > 0))
{
//If it is, check is we're running or cranking
if(currentStatus.RPM > ((unsigned int)configPage2.crankRPM * 100)) //Crank RPM stored in byte as RPM / 100
{ //Sets the engine running bit, clears the engine cranking bit
BIT_SET(currentStatus.engine, BIT_ENGINE_RUN);
BIT_CLEAR(currentStatus.engine, BIT_ENGINE_CRANK);
if(startRevolutions >= configPage2.StgCycles) { ignitionOn = true; fuelOn = true;}
}
else
{ //Sets the engine cranking bit, clears the engine running bit
BIT_SET(currentStatus.engine, BIT_ENGINE_CRANK);
BIT_CLEAR(currentStatus.engine, BIT_ENGINE_RUN);
currentStatus.runSecs = 0; //We're cranking (hopefully), so reset the engine run time to prompt ASE.
//Check whether enough cranking revolutions have been performed to turn the ignition on
if(startRevolutions >= configPage2.StgCycles) { ignitionOn = true; fuelOn = true;}
}
idleControl(); //Perform any idle realted actions
//END SETTING STATUSES
//-----------------------------------------------------------------------------------------------------
//Begin the fuel calculation
//Calculate an injector pulsewidth from the VE
currentStatus.corrections = correctionsTotal();
//currentStatus.corrections = 100;
if (configPage1.algorithm == 0) //Check which fuelling algorithm is being used
{
//Speed Density
currentStatus.VE = get3DTableValue(&fuelTable, currentStatus.MAP, currentStatus.RPM); //Perform lookup into fuel map for RPM vs MAP value
currentStatus.PW = PW_SD(req_fuel_uS, currentStatus.VE, currentStatus.MAP, currentStatus.corrections, inj_opentime_uS);
currentStatus.advance = get3DTableValue(&ignitionTable, currentStatus.MAP, currentStatus.RPM); //As above, but for ignition advance
}
else
{
//Alpha-N
currentStatus.VE = get3DTableValue(&fuelTable, currentStatus.TPS, currentStatus.RPM); //Perform lookup into fuel map for RPM vs TPS value
currentStatus.PW = PW_AN(req_fuel_uS, currentStatus.VE, currentStatus.TPS, currentStatus.corrections, inj_opentime_uS); //Calculate pulsewidth using the Alpha-N algorithm (in uS)
currentStatus.advance = get3DTableValue(&ignitionTable, currentStatus.TPS, currentStatus.RPM); //As above, but for ignition advance
}
//Check for fixed ignition angles
if (configPage2.FixAng != 0) { currentStatus.advance = configPage2.FixAng; } //Check whether the user has set a fixed timing angle
if ( BIT_CHECK(currentStatus.engine, BIT_ENGINE_CRANK) ) { currentStatus.advance = configPage2.CrankAng; } //Use the fixed cranking ignition angle
//Adjust the advance based on IAT
currentStatus.advance -= table2D_getValue(&IATRetardTable, currentStatus.IAT);
int injector1StartAngle = 0;
int injector2StartAngle = 0;
int injector3StartAngle = 0; //Currently used for 3 cylinder only
int injector4StartAngle = 0; //Not used until sequential gets written
int ignition1StartAngle = 0;
int ignition2StartAngle = 0;
int ignition3StartAngle = 0; //Not used until sequential or 4+ cylinders support gets written
int ignition4StartAngle = 0; //Not used until sequential or 4+ cylinders support gets written
//These are used for comparisons on channels above 1 where the starting angle (for injectors or ignition) can be less than a single loop time
//(Don't ask why this is needed, it will break your head)
int tempCrankAngle;
int tempStartAngle;
//********************************************************
//How fast are we going? Need to know how long (uS) it will take to get from one tooth to the next. We then use that to estimate how far we are between the last tooth and the next one
//We use a 1st Deriv accleration prediction, but only when there is an even spacing between primary sensor teeth
//Any decoder that has uneven spacing has its triggerToothAngle set to 0
if(secondDerivEnabled && toothHistoryIndex >= 3 && currentStatus.RPM < 2000) //toothHistoryIndex must be greater than or equal to 3 as we need the last 3 entries. Currently this mode only runs below 3000 rpm
{
//Only recalculate deltaV if the tooth has changed since last time (DeltaV stays the same until the next tooth)
//if (deltaToothCount != toothCurrentCount)
{
deltaToothCount = toothCurrentCount;
int angle1, angle2; //These represent the crank angles that are travelled for the last 2 pulses
if(configPage2.TrigPattern == 4)
{
//Special case for 70/110 pattern on 4g63
angle2 = triggerToothAngle; //Angle 2 is the most recent
if (angle2 == 70) { angle1 = 110; }
else { angle1 = 70; }
}
else if(configPage2.TrigPattern == 0)
{
//Special case for missing tooth decoder where the missing tooth was one of the last 2 seen
if(toothCurrentCount == 1) { angle2 = 2*triggerToothAngle; angle1 = triggerToothAngle; }
else if(toothCurrentCount == 2) { angle1 = 2*triggerToothAngle; angle2 = triggerToothAngle; }
else { angle1 = angle2 = triggerToothAngle; }
}
else { angle1 = angle2 = triggerToothAngle; }
long toothDeltaV = (1000000L * angle2 / toothHistory[toothHistoryIndex]) - (1000000L * angle1 / toothHistory[toothHistoryIndex-1]);
long toothDeltaT = toothHistory[toothHistoryIndex];
long timeToLastTooth = micros() - toothLastToothTime; //Cannot be unsigned
rpmDelta = (toothDeltaV << 10) / (6 * toothDeltaT);
}
timePerDegree = ldiv( 166666L, (currentStatus.RPM + rpmDelta)).quot; //There is a small amount of rounding in this calculation, however it is less than 0.001 of a uS (Faster as ldiv than / )
}
else
{
timePerDegree = ldiv( 166666L, currentStatus.RPM ).quot; //There is a small amount of rounding in this calculation, however it is less than 0.001 of a uS (Faster as ldiv than / )
}
//Check that the duty cycle of the chosen pulsewidth isn't too high. This is disabled at cranking
if( !BIT_CHECK(currentStatus.engine, BIT_ENGINE_CRANK) )
{
unsigned long pwLimit = percentage(configPage1.dutyLim, revolutionTime); //The pulsewidth limit is determined to be the duty cycle limit (Eg 85%) by the total time it takes to perform 1 revolution
if (currentStatus.PW > pwLimit) { currentStatus.PW = pwLimit; }
}
//***********************************************************************************************
//BEGIN INJECTION TIMING
//Determine next firing angles
int PWdivTimerPerDegree = div(currentStatus.PW, timePerDegree).quot; //How many crank degrees the calculated PW will take at the current speed
injector1StartAngle = configPage1.inj1Ang - ( PWdivTimerPerDegree ); //This is a little primitive, but is based on the idea that all fuel needs to be delivered before the inlet valve opens. See http://www.extraefi.co.uk/sequential_fuel.html for more detail
if(injector1StartAngle < 0) {injector1StartAngle += 360;}
//Repeat the above for each cylinder
switch (configPage1.nCylinders)
{
//2 cylinders
case 2:
injector2StartAngle = (configPage1.inj2Ang + channel2InjDegrees - ( PWdivTimerPerDegree ));
if(injector2StartAngle > 360) {injector2StartAngle -= 360;}
break;
//3 cylinders
case 3:
injector2StartAngle = (configPage1.inj2Ang + channel2InjDegrees - ( PWdivTimerPerDegree ));
if(injector2StartAngle > 360) {injector2StartAngle -= 360;}
injector3StartAngle = (configPage1.inj3Ang + channel3InjDegrees - ( PWdivTimerPerDegree ));
if(injector3StartAngle > 360) {injector3StartAngle -= 360;}
break;
//4 cylinders
case 4:
injector2StartAngle = (configPage1.inj2Ang + channel2InjDegrees - ( PWdivTimerPerDegree ));
if(injector2StartAngle > 360) {injector2StartAngle -= 360;}
break;
//6 cylinders
case 6:
injector2StartAngle = (configPage1.inj2Ang + channel2InjDegrees - ( PWdivTimerPerDegree ));
if(injector2StartAngle > 360) {injector2StartAngle -= 360;}
injector3StartAngle = (configPage1.inj3Ang + channel3InjDegrees - ( PWdivTimerPerDegree ));
if(injector3StartAngle > 360) {injector3StartAngle -= 360;}
break;
//8 cylinders
case 8:
injector2StartAngle = (configPage1.inj2Ang + channel2InjDegrees - ( PWdivTimerPerDegree ));
if(injector2StartAngle > 360) {injector2StartAngle -= 360;}
injector3StartAngle = (configPage1.inj3Ang + channel3InjDegrees - ( PWdivTimerPerDegree ));
if(injector3StartAngle > 360) {injector3StartAngle -= 360;}
injector4StartAngle = (configPage1.inj4Ang + channel4InjDegrees - ( PWdivTimerPerDegree ));
if(injector4StartAngle > 360) {injector4StartAngle -= 360;}
break;
//Will hit the default case on 1 cylinder or >8 cylinders. Do nothing in these cases
default:
break;
}
//***********************************************************************************************
//| BEGIN IGNITION CALCULATIONS
if (currentStatus.RPM > ((unsigned int)(configPage2.SoftRevLim) * 100) ) { currentStatus.advance = configPage2.SoftLimRetard; } //Softcut RPM limit (If we're above softcut limit, delay timing by configured number of degrees)
if (configPage3.launchEnabled && currentStatus.launching && (currentStatus.RPM > ((unsigned int)(configPage3.lnchSoftLim) * 100)) ) { currentStatus.advance = configPage3.lnchRetard; } //SoftCut rev limit for 2-step launch control
//Set dwell
//Dwell is stored as ms * 10. ie Dwell of 4.3ms would be 43 in configPage2. This number therefore needs to be multiplied by 100 to get dwell in uS
if ( BIT_CHECK(currentStatus.engine, BIT_ENGINE_CRANK) ) { currentStatus.dwell = (configPage2.dwellCrank * 100); }
else { currentStatus.dwell = (configPage2.dwellRun * 100); }
//Pull battery voltage based dwell correction and apply if needed
currentStatus.dwellCorrection = table2D_getValue(&dwellVCorrectionTable, currentStatus.battery10);
if (currentStatus.dwellCorrection != 100) { currentStatus.dwell = divs100(currentStatus.dwell) * currentStatus.dwellCorrection; }
int dwellAngle = (div(currentStatus.dwell, timePerDegree).quot ); //Convert the dwell time to dwell angle based on the current engine speed
//Calculate start angle for each channel
//1 cylinder (Everyone gets this)
ignition1StartAngle = 360 - currentStatus.advance - dwellAngle; // 360 - desired advance angle - number of degrees the dwell will take
if(ignition1StartAngle < 0) {ignition1StartAngle += 360;}
//This test for more cylinders and do the same thing
switch (configPage1.nCylinders)
{
//2 cylinders
case 2:
ignition2StartAngle = channel2IgnDegrees + 360 - currentStatus.advance - dwellAngle;
if(ignition2StartAngle > 360) {ignition2StartAngle -= 360;}
break;
//3 cylinders
case 3:
ignition2StartAngle = channel2IgnDegrees + 360 - currentStatus.advance - dwellAngle;
if(ignition2StartAngle > 360) {ignition2StartAngle -= 360;}
ignition3StartAngle = channel3IgnDegrees + 360 - currentStatus.advance - dwellAngle;
if(ignition3StartAngle > 360) {ignition3StartAngle -= 360;}
break;
//4 cylinders
case 4:
ignition2StartAngle = channel2IgnDegrees + 360 - currentStatus.advance - dwellAngle;
if(ignition2StartAngle > 360) {ignition2StartAngle -= 360;}
if(ignition2StartAngle < 0) {ignition2StartAngle += 360;}
break;
//6 cylinders
case 6:
ignition2StartAngle = channel2IgnDegrees + 360 - currentStatus.advance - dwellAngle;
if(ignition2StartAngle > 360) {ignition2StartAngle -= 360;}
ignition3StartAngle = channel3IgnDegrees + 360 - currentStatus.advance - dwellAngle;
if(ignition3StartAngle > 360) {ignition3StartAngle -= 360;}
break;
//8 cylinders
case 8:
ignition2StartAngle = channel2IgnDegrees + 360 - currentStatus.advance - dwellAngle;
if(ignition2StartAngle > 360) {ignition2StartAngle -= 360;}
ignition3StartAngle = channel3IgnDegrees + 360 - currentStatus.advance - dwellAngle;
if(ignition3StartAngle > 360) {ignition3StartAngle -= 360;}
ignition4StartAngle = channel4IgnDegrees + 360 - currentStatus.advance - dwellAngle;
if(ignition4StartAngle > 360) {ignition4StartAngle -= 360;}
break;
//Will hit the default case on 1 cylinder or >8 cylinders. Do nothing in these cases
default:
break;
}
//***********************************************************************************************
//| BEGIN FUEL SCHEDULES
//Finally calculate the time (uS) until we reach the firing angles and set the schedules
//We only need to set the shcedule if we're BEFORE the open angle
//This may potentially be called a number of times as we get closer and closer to the opening time
//Determine the current crank angle
int crankAngle = getCrankAngle(timePerDegree);
if (fuelOn && currentStatus.PW > 0)
{
if (injector1StartAngle > crankAngle)
{
if (configPage1.injLayout == 1)
{
setFuelSchedule1(openInjector1and4,
((unsigned long)(injector1StartAngle - crankAngle) * (unsigned long)timePerDegree),
(unsigned long)currentStatus.PW,
closeInjector1and4
);
}
else
{
setFuelSchedule1(openInjector1,
((unsigned long)(injector1StartAngle - crankAngle) * (unsigned long)timePerDegree),
(unsigned long)currentStatus.PW,
closeInjector1
);
}
}
/*-----------------------------------------------------------------------------------------
| A Note on tempCrankAngle and tempStartAngle:
| The use of tempCrankAngle/tempStartAngle is described below. It is then used in the same way for channels 2, 3 and 4 on both injectors and ignition
| Essentially, these 2 variables are used to realign the current crank and and the desired start angle around 0 degrees for the given cylinder/output
| Eg: If cylinder 2 TDC is 180 degrees after cylinder 1 (Eg a standard 4 cylidner engine), then tempCrankAngle is 180* less than the current crank angle and
| tempStartAngle is the desired open time less 180*. Thus the cylinder is being treated relative to its own TDC, regardless of its offset
|
| This is done to avoid problems with very short of very long times until tempStartAngle.
| This will very likely need to be rewritten when sequential is enabled
|------------------------------------------------------------------------------------------
*/
tempCrankAngle = crankAngle - channel2InjDegrees;
if( tempCrankAngle < 0) { tempCrankAngle += 360; }
tempStartAngle = injector2StartAngle - channel2InjDegrees;
if ( tempStartAngle < 0) { tempStartAngle += 360; }
if (tempStartAngle > tempCrankAngle)
{
if (configPage1.injLayout == 1)
{
setFuelSchedule2(openInjector2and3,
((unsigned long)(tempStartAngle - tempCrankAngle) * (unsigned long)timePerDegree),
(unsigned long)currentStatus.PW,
closeInjector2and3
);
}
else
{
setFuelSchedule2(openInjector2,
((unsigned long)(tempStartAngle - tempCrankAngle) * (unsigned long)timePerDegree),
(unsigned long)currentStatus.PW,
closeInjector2