Skip to content

Commit 14f0ca6

Browse files
committed
Move calculateIgnitionAngles() to scheduler.*
1 parent 39c487a commit 14f0ca6

File tree

6 files changed

+223
-86
lines changed

6 files changed

+223
-86
lines changed

speeduino/scheduler.cpp

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ FuelSchedule fuelSchedule7(FUEL7_COUNTER, FUEL7_COMPARE); //cppcheck-suppress mi
5252
FuelSchedule fuelSchedule8(FUEL8_COUNTER, FUEL8_COMPARE); //cppcheck-suppress misra-c2012-8.4
5353
#endif
5454

55+
static table2D_u8_u8_8 rotarySplitTable(&configPage10.rotarySplitBins, &configPage10.rotarySplitValues);
56+
5557
IgnitionSchedule ignitionSchedule1(IGN1_COUNTER, IGN1_COMPARE); //cppcheck-suppress misra-c2012-8.4
5658
IgnitionSchedule ignitionSchedule2(IGN2_COUNTER, IGN2_COMPARE); //cppcheck-suppress misra-c2012-8.4
5759
IgnitionSchedule ignitionSchedule3(IGN3_COUNTER, IGN3_COMPARE); //cppcheck-suppress misra-c2012-8.4
@@ -750,7 +752,7 @@ static inline void changeIgnitionToFullSequential(const config2 &page2, statuses
750752
}
751753
}
752754

753-
void matchIgnitionSchedulersToSyncState(const config2 &page2, const config4 &page4, const decoder_status_t &decoderStatus, statuses &current)
755+
TESTABLE_INLINE_STATIC void matchIgnitionSchedulersToSyncState(const config2 &page2, const config4 &page4, const decoder_status_t &decoderStatus, statuses &current)
754756
{
755757
if (isFullSequentialIgnition(page4, decoderStatus) && ( CRANK_ANGLE_MAX_IGN != 720 )) {
756758
changeIgnitionToFullSequential(page2, current);
@@ -760,3 +762,92 @@ void matchIgnitionSchedulersToSyncState(const config2 &page2, const config4 &pag
760762
// Ignition layout matches current sync - nothing to do but keep MISRA checker happy
761763
}
762764
}
765+
766+
767+
static inline void calculateRotaryIgnitionAngles(uint16_t dwellAngle, const statuses &current)
768+
{
769+
#if IGN_CHANNELS>=4
770+
calculateIgnitionAngles(ignitionSchedule1, dwellAngle, current.advance);
771+
calculateIgnitionAngles(ignitionSchedule2, dwellAngle, current.advance);
772+
uint8_t splitDegrees = table2D_getValue(&rotarySplitTable, (uint8_t)current.ignLoad);
773+
774+
//The trailing angles are set relative to the leading ones
775+
calculateIgnitionTrailingRotary(ignitionSchedule1, dwellAngle, splitDegrees, ignitionSchedule3);
776+
calculateIgnitionTrailingRotary(ignitionSchedule2, dwellAngle, splitDegrees, ignitionSchedule4);
777+
#endif
778+
}
779+
780+
static inline void calculateNonRotaryIgnitionAngles(uint16_t dwellAngle, const statuses &current)
781+
{
782+
switch (current.maxIgnOutputs)
783+
{
784+
case 8:
785+
#if IGN_CHANNELS >= 8
786+
calculateIgnitionAngles(ignitionSchedule8, dwellAngle, current.advance);
787+
#endif
788+
[[gnu::fallthrough]];
789+
//cppcheck-suppress misra-c2012-16.3
790+
case 7:
791+
#if IGN_CHANNELS >= 7
792+
calculateIgnitionAngles(ignitionSchedule7, dwellAngle, current.advance);
793+
#endif
794+
[[gnu::fallthrough]];
795+
//cppcheck-suppress misra-c2012-16.3
796+
case 6:
797+
#if IGN_CHANNELS >= 6
798+
calculateIgnitionAngles(ignitionSchedule6, dwellAngle, current.advance);
799+
#endif
800+
[[gnu::fallthrough]];
801+
//cppcheck-suppress misra-c2012-16.3
802+
case 5:
803+
#if IGN_CHANNELS >= 5
804+
calculateIgnitionAngles(ignitionSchedule5, dwellAngle, current.advance);
805+
#endif
806+
[[gnu::fallthrough]];
807+
//cppcheck-suppress misra-c2012-16.3
808+
case 4:
809+
#if IGN_CHANNELS >= 4
810+
calculateIgnitionAngles(ignitionSchedule4, dwellAngle, current.advance);
811+
#endif
812+
[[gnu::fallthrough]];
813+
//cppcheck-suppress misra-c2012-16.3
814+
case 3:
815+
#if IGN_CHANNELS >= 3
816+
calculateIgnitionAngles(ignitionSchedule3, dwellAngle, current.advance);
817+
#endif
818+
[[gnu::fallthrough]];
819+
//cppcheck-suppress misra-c2012-16.3
820+
case 2:
821+
#if IGN_CHANNELS >= 2
822+
calculateIgnitionAngles(ignitionSchedule2, dwellAngle, current.advance);
823+
#endif
824+
[[gnu::fallthrough]];
825+
//cppcheck-suppress misra-c2012-16.3
826+
case 1:
827+
calculateIgnitionAngles(ignitionSchedule1, dwellAngle, current.advance);
828+
break;
829+
default:
830+
// Do nothing
831+
break;
832+
}
833+
}
834+
835+
/** Calculate the Ignition angles for all cylinders (based on @ref config2.nCylinders).
836+
* both start and end angles are calculated for each channel.
837+
* Also the mode of ignition firing - wasted spark vs. dedicated spark per cyl. - is considered here.
838+
*/
839+
void __attribute__((flatten)) calculateIgnitionAngles(const config2 &page2, const config4 &page4, const decoder_status_t &decoderStatus, statuses &current)
840+
{
841+
matchIgnitionSchedulersToSyncState(page2, page4, decoderStatus, current);
842+
843+
uint16_t dwellAngle = timeToAngleDegPerMicroSec(current.dwell);
844+
845+
if((current.maxIgnOutputs==4U) && (page4.sparkMode == IGN_MODE_ROTARY))
846+
{
847+
calculateRotaryIgnitionAngles(dwellAngle, current);
848+
}
849+
else
850+
{
851+
calculateNonRotaryIgnitionAngles(dwellAngle, current);
852+
}
853+
}

speeduino/scheduler.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,6 @@ extern FuelSchedule fuelSchedule8;
320320
void changeHalfToFullSync(const config2 &page2, statuses &current);
321321
void changeFullToHalfSync(const config2 &page2, const config4 &page4, statuses &current);
322322

323-
void matchIgnitionSchedulersToSyncState(const config2 &page2, const config4 &page4, const decoder_status_t &decoderStatus, statuses &current);
323+
void calculateIgnitionAngles(const config2 &page2, const config4 &page4, const decoder_status_t &decoderStatus, statuses &current);
324324

325325
#endif // SCHEDULER_H

speeduino/speeduino.ino

Lines changed: 1 addition & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
5656
#define CRANK_RUN_HYSTER 15
5757

5858
// Forward declarations
59-
void calculateIgnitionAngles(uint16_t dwellAngle);
6059
void checkLaunchAndFlatShift();
6160

6261
uint8_t ignitionChannelsOn; /**< The current state of the ignition system (on or off) */
@@ -66,7 +65,6 @@ uint32_t rollingCutLastRev = 0; /**< Tracks whether we're on the same or a diffe
6665
uint32_t revLimitAllowedEndTime = 0;
6766

6867
static table2D_u8_u16_4 injectorAngleTable(&configPage2.injAngRPM, &configPage2.injAng);
69-
static table2D_u8_u8_8 rotarySplitTable(&configPage10.rotarySplitBins, &configPage10.rotarySplitValues);
7068
static table2D_i8_u8_4 rollingCutTable(&configPage15.rollingProtRPMDelta, &configPage15.rollingProtCutPercent);
7169
static table2D_u8_u8_10 idleTargetTable(&configPage6.iacBins, &configPage6.iacCLValues);
7270

@@ -819,7 +817,7 @@ BEGIN_LTO_ALWAYS_INLINE(void) loop(void)
819817
currentStatus.dwell = correctionsDwell(computeDwell(currentStatus, configPage2, configPage4, dwellTable));
820818

821819
// Convert the dwell time to dwell angle based on the current engine speed
822-
calculateIgnitionAngles(timeToAngleDegPerMicroSec(currentStatus.dwell));
820+
calculateIgnitionAngles(configPage2, configPage4, getDecoderStatus(), currentStatus);
823821

824822
//If ignition timing is being tracked per tooth, perform the calcs to get the end teeth
825823
//This only needs to be run if the advance figure has changed, otherwise the end teeth will still be the same
@@ -1041,87 +1039,6 @@ END_LTO_INLINE()
10411039

10421040
#endif //Unit test guard
10431041

1044-
static inline void calculateRotaryIgnitionAngles(uint16_t dwellAngle, const statuses &current)
1045-
{
1046-
#if IGN_CHANNELS>=4
1047-
calculateIgnitionAngles(ignitionSchedule1, dwellAngle, current.advance);
1048-
calculateIgnitionAngles(ignitionSchedule2, dwellAngle, current.advance);
1049-
uint8_t splitDegrees = table2D_getValue(&rotarySplitTable, (uint8_t)current.ignLoad);
1050-
1051-
//The trailing angles are set relative to the leading ones
1052-
calculateIgnitionTrailingRotary(ignitionSchedule1, dwellAngle, splitDegrees, ignitionSchedule3);
1053-
calculateIgnitionTrailingRotary(ignitionSchedule2, dwellAngle, splitDegrees, ignitionSchedule4);
1054-
#endif
1055-
}
1056-
1057-
static inline void calculateNonRotaryIgnitionAngles(uint16_t dwellAngle, const statuses &current)
1058-
{
1059-
switch (current.maxIgnOutputs)
1060-
{
1061-
case 8:
1062-
#if IGN_CHANNELS >= 8
1063-
calculateIgnitionAngles(ignitionSchedule8, dwellAngle, current.advance);
1064-
#endif
1065-
[[gnu::fallthrough]];
1066-
case 7:
1067-
#if IGN_CHANNELS >= 7
1068-
calculateIgnitionAngles(ignitionSchedule7, dwellAngle, current.advance);
1069-
#endif
1070-
[[gnu::fallthrough]];
1071-
case 6:
1072-
#if IGN_CHANNELS >= 6
1073-
calculateIgnitionAngles(ignitionSchedule6, dwellAngle, current.advance);
1074-
#endif
1075-
[[gnu::fallthrough]];
1076-
case 5:
1077-
#if IGN_CHANNELS >= 5
1078-
calculateIgnitionAngles(ignitionSchedule5, dwellAngle, current.advance);
1079-
#endif
1080-
[[gnu::fallthrough]];
1081-
case 4:
1082-
#if IGN_CHANNELS >= 4
1083-
calculateIgnitionAngles(ignitionSchedule4, dwellAngle, current.advance);
1084-
#endif
1085-
[[gnu::fallthrough]];
1086-
case 3:
1087-
#if IGN_CHANNELS >= 3
1088-
calculateIgnitionAngles(ignitionSchedule3, dwellAngle, current.advance);
1089-
#endif
1090-
[[gnu::fallthrough]];
1091-
case 2:
1092-
#if IGN_CHANNELS >= 2
1093-
calculateIgnitionAngles(ignitionSchedule2, dwellAngle, current.advance);
1094-
#endif
1095-
[[gnu::fallthrough]];
1096-
case 1:
1097-
calculateIgnitionAngles(ignitionSchedule1, dwellAngle, current.advance);
1098-
[[gnu::fallthrough]];
1099-
default:
1100-
// Do nothing
1101-
break;
1102-
}
1103-
}
1104-
1105-
/** Calculate the Ignition angles for all cylinders (based on @ref config2.nCylinders).
1106-
* both start and end angles are calculated for each channel.
1107-
* Also the mode of ignition firing - wasted spark vs. dedicated spark per cyl. - is considered here.
1108-
*/
1109-
void __attribute__((flatten)) calculateIgnitionAngles(uint16_t dwellAngle)
1110-
{
1111-
matchIgnitionSchedulersToSyncState(configPage2, configPage4, getDecoderStatus(), currentStatus);
1112-
1113-
if (currentStatus.maxIgnOutputs==4U && (configPage4.sparkMode == IGN_MODE_ROTARY))
1114-
{
1115-
//Rotary mode with 4 outputs
1116-
calculateRotaryIgnitionAngles(dwellAngle, currentStatus);
1117-
}
1118-
else
1119-
{
1120-
//Non-rotary mode
1121-
calculateNonRotaryIgnitionAngles(dwellAngle, currentStatus);
1122-
}
1123-
}
1124-
11251042
void checkLaunchAndFlatShift()
11261043
{
11271044
//Check for launching/flat shift (clutch) based on the current and previous clutch states

test/test_init/test_ignition_schedule_init.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,8 @@ static void assert_4cylinder_half_sync(void)
340340
assert_ignition_schedules(360U, 2U, angle);
341341
}
342342

343+
extern void matchIgnitionSchedulersToSyncState(const config2 &page2, const config4 &page4, const decoder_status_t &decoderStatus, statuses &current);
344+
343345
static void setupPartialSyncTest(uint8_t cylinders)
344346
{
345347
prepareForInitialiseAll(3U);

test/test_schedules/main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ void runAllScheduleTests(void)
1818
extern void test_ignition_controller();
1919
extern void test_fuel_controller(void);
2020
extern void test_overdwell(void);
21+
extern void test_ignition_schedule_controller();
2122

2223
initialiseAll();
2324

@@ -35,6 +36,7 @@ void runAllScheduleTests(void)
3536
test_ignition_controller();
3637
test_fuel_controller();
3738
test_overdwell();
39+
test_ignition_schedule_controller();
3840
}
3941

4042
TEST_HARNESS(runAllScheduleTests)
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#include <unity.h>
2+
#include "../test_utils.h"
3+
#include "scheduler.h"
4+
#include "channel_test_helpers.h"
5+
6+
extern void SetRevolutionTime(uint32_t revTime);
7+
8+
struct ignition_test_context_t
9+
{
10+
config2 page2 = {};
11+
config4 page4 = {};
12+
decoder_status_t decoderStatus = {};
13+
statuses current = {};
14+
15+
ignition_test_context_t() {
16+
// Set basic engine config defaults
17+
page2.nCylinders = 4U;
18+
page2.strokes = FOUR_STROKE;
19+
page4.sparkMode = IGN_MODE_WASTED;
20+
decoderStatus.syncStatus = SyncStatus::Full;
21+
current.maxIgnOutputs = 4U;
22+
current.dwell = 3000U; // 3ms dwell
23+
current.advance = 15U; // 15 degrees advance
24+
SetRevolutionTime(UDIV_ROUND_CLOSEST(60UL*1000000UL, 4000, uint32_t));
25+
}
26+
27+
void calculateIgnitionAngles(void)
28+
{
29+
::calculateIgnitionAngles(page2, page4, decoderStatus, current);
30+
}
31+
};
32+
33+
static void setup_ignition_channel_angles(void)
34+
{
35+
RUNIF_IGNCHANNEL1( { ignitionSchedule1.channelDegrees = (CRANK_ANGLE_MAX_IGN/8U) * 0U; }, {});
36+
RUNIF_IGNCHANNEL2( { ignitionSchedule2.channelDegrees = (CRANK_ANGLE_MAX_IGN/8U) * 1U; }, {});
37+
RUNIF_IGNCHANNEL3( { ignitionSchedule3.channelDegrees = (CRANK_ANGLE_MAX_IGN/8U) * 2U; }, {});
38+
RUNIF_IGNCHANNEL4( { ignitionSchedule4.channelDegrees = (CRANK_ANGLE_MAX_IGN/8U) * 3U; }, {});
39+
RUNIF_IGNCHANNEL5( { ignitionSchedule5.channelDegrees = (CRANK_ANGLE_MAX_IGN/8U) * 4U; }, {});
40+
RUNIF_IGNCHANNEL6( { ignitionSchedule6.channelDegrees = (CRANK_ANGLE_MAX_IGN/8U) * 5U; }, {});
41+
RUNIF_IGNCHANNEL7( { ignitionSchedule7.channelDegrees = (CRANK_ANGLE_MAX_IGN/8U) * 6U; }, {});
42+
RUNIF_IGNCHANNEL8( { ignitionSchedule8.channelDegrees = (CRANK_ANGLE_MAX_IGN/8U) * 7U; }, {});
43+
}
44+
45+
static void assert_ignition_angles(const ignition_test_context_t &context)
46+
{
47+
// We only need to confirm the calculations were run. There
48+
// are separate detailed tests for the calculations.
49+
RUNIF_IGNCHANNEL1( { if (context.current.maxIgnOutputs>=1) { TEST_ASSERT_GREATER_THAN(0U, ignitionSchedule1.chargeAngle + ignitionSchedule1.dischargeAngle); }}, {});
50+
RUNIF_IGNCHANNEL2( { if (context.current.maxIgnOutputs>=2) { TEST_ASSERT_GREATER_THAN(0U, ignitionSchedule2.chargeAngle + ignitionSchedule2.dischargeAngle); }}, {});
51+
RUNIF_IGNCHANNEL3( { if (context.current.maxIgnOutputs>=3) { TEST_ASSERT_GREATER_THAN(0U, ignitionSchedule3.chargeAngle + ignitionSchedule3.dischargeAngle); }}, {});
52+
RUNIF_IGNCHANNEL4( { if (context.current.maxIgnOutputs>=4) { TEST_ASSERT_GREATER_THAN(0U, ignitionSchedule4.chargeAngle + ignitionSchedule4.dischargeAngle); }}, {});
53+
RUNIF_IGNCHANNEL5( { if (context.current.maxIgnOutputs>=5) { TEST_ASSERT_GREATER_THAN(0U, ignitionSchedule5.chargeAngle + ignitionSchedule5.dischargeAngle); }}, {});
54+
RUNIF_IGNCHANNEL6( { if (context.current.maxIgnOutputs>=6) { TEST_ASSERT_GREATER_THAN(0U, ignitionSchedule6.chargeAngle + ignitionSchedule6.dischargeAngle); }}, {});
55+
RUNIF_IGNCHANNEL7( { if (context.current.maxIgnOutputs>=7) { TEST_ASSERT_GREATER_THAN(0U, ignitionSchedule7.chargeAngle + ignitionSchedule7.dischargeAngle); }}, {});
56+
RUNIF_IGNCHANNEL8( { if (context.current.maxIgnOutputs>=8) { TEST_ASSERT_GREATER_THAN(0U, ignitionSchedule8.chargeAngle + ignitionSchedule8.dischargeAngle); }}, {});
57+
}
58+
59+
static void test_calculateIgnitionAngles_nonrotary(void)
60+
{
61+
ignition_test_context_t context;
62+
CRANK_ANGLE_MAX_IGN = 720U;
63+
context.page2.nCylinders = 8U;
64+
context.current.maxIgnOutputs = 8U;
65+
context.page4.sparkMode = IGN_MODE_SEQUENTIAL;
66+
context.decoderStatus.syncStatus = SyncStatus::Full;
67+
68+
setup_ignition_channel_angles();
69+
context.calculateIgnitionAngles();
70+
assert_ignition_angles(context);
71+
}
72+
73+
static void test_calculateIgnitionAngles_rotary(void)
74+
{
75+
ignition_test_context_t context;
76+
context.current.maxIgnOutputs = 4U;
77+
context.page4.sparkMode = IGN_MODE_ROTARY;
78+
context.page2.nCylinders = 4U;
79+
80+
setup_ignition_channel_angles();
81+
context.calculateIgnitionAngles();
82+
assert_ignition_angles(context);
83+
}
84+
85+
static void test_calculateIgnitionAngles_rotary_non_4_output_uses_non_rotary(void)
86+
{
87+
ignition_test_context_t context;
88+
context.current.maxIgnOutputs = 5U; // Not 4
89+
context.page4.sparkMode = IGN_MODE_ROTARY;
90+
91+
setup_ignition_channel_angles();
92+
context.calculateIgnitionAngles();
93+
94+
// Even though sparkMode is ROTARY, if maxIgnOutputs != 4, non-rotary path is used
95+
TEST_ASSERT_NOT_EQUAL(0U, ignitionSchedule5.chargeAngle + ignitionSchedule5.dischargeAngle);
96+
}
97+
98+
static void test_calculateIgnitionAngles_sync_state_transitions(void)
99+
{
100+
ignition_test_context_t context;
101+
context.page2.nCylinders = 4U;
102+
context.page4.sparkMode = IGN_MODE_SEQUENTIAL;
103+
context.current.maxIgnOutputs = 4U;
104+
105+
// Test transition from no sync to full sync
106+
context.decoderStatus.syncStatus = SyncStatus::Full;
107+
context.calculateIgnitionAngles();
108+
TEST_ASSERT_EQUAL_UINT16(720U, CRANK_ANGLE_MAX_IGN);
109+
110+
// Re-setup context for partial sync
111+
context.decoderStatus.syncStatus = SyncStatus::Partial;
112+
context.current.maxIgnOutputs = 4U; // Reset for next call
113+
context.calculateIgnitionAngles();
114+
TEST_ASSERT_EQUAL_UINT16(360U, CRANK_ANGLE_MAX_IGN);
115+
}
116+
117+
void test_ignition_schedule_controller(void)
118+
{
119+
SET_UNITY_FILENAME() {
120+
RUN_TEST(test_calculateIgnitionAngles_nonrotary);
121+
RUN_TEST(test_calculateIgnitionAngles_rotary);
122+
RUN_TEST(test_calculateIgnitionAngles_rotary_non_4_output_uses_non_rotary);
123+
RUN_TEST(test_calculateIgnitionAngles_sync_state_transitions);
124+
}
125+
}

0 commit comments

Comments
 (0)