Line | Branch | Decision | Exec | Source |
---|---|---|---|---|
1 | /** | |||
2 | * @file pwm_generator_logic.cpp | |||
3 | * | |||
4 | * This PWM implementation keep track of when it would be the next time to toggle the signal. | |||
5 | * It constantly sets timer to that next toggle time, then sets the timer again from the callback, and so on. | |||
6 | * | |||
7 | * @date Mar 2, 2014 | |||
8 | * @author Andrey Belomutskiy, (c) 2012-2020 | |||
9 | */ | |||
10 | ||||
11 | #include "pch.h" | |||
12 | ||||
13 | ||||
14 | #if EFI_PROD_CODE | |||
15 | #include "mpu_util.h" | |||
16 | #include "gpio_ext.h" | |||
17 | #endif // EFI_PROD_CODE | |||
18 | ||||
19 | // 1% duty cycle | |||
20 | #define ZERO_PWM_THRESHOLD 0.01 | |||
21 | // 99% duty cycle | |||
22 | #define FULL_PWM_THRESHOLD 0.99 | |||
23 | ||||
24 | 23 | SimplePwm::SimplePwm() | ||
25 | { | |||
26 | 23 | seq.waveCount = 1; | ||
27 | 23 | seq.phaseCount = 2; | ||
28 | 23 | } | ||
29 | ||||
30 | 9 | SimplePwm::SimplePwm(const char *name) : SimplePwm() { | ||
31 | 9 | m_name = name; | ||
32 | 9 | } | ||
33 | ||||
34 | 26 | PwmConfig::PwmConfig() { | ||
35 | 26 | memset((void*)&scheduling, 0, sizeof(scheduling)); | ||
36 | 26 | memset((void*)&safe, 0, sizeof(safe)); | ||
37 | 26 | dbgNestingLevel = 0; | ||
38 | 26 | periodNt = NAN; | ||
39 | 26 | mode = PM_NORMAL; | ||
40 | 26 | memset(&outputPins, 0, sizeof(outputPins)); | ||
41 | 26 | m_name = "[noname]"; | ||
42 | 26 | } | ||
43 | ||||
44 | /** | |||
45 | * This method allows you to change duty cycle on the fly | |||
46 | * @param dutyCycle value between 0 and 1 | |||
47 | * See also setFrequency | |||
48 | */ | |||
49 | 2187 | void SimplePwm::setSimplePwmDutyCycle(float dutyCycle) { | ||
50 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2187 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 2187 times.
|
2187 | if (isStopRequested) { |
51 | // we are here in order to not change pin once PWM stop was requested | |||
52 | ✗ | return; | ||
53 | } | |||
54 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 2187 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 2187 times.
|
2187 | if (std::isnan(dutyCycle)) { |
55 | ✗ | warning(ObdCode::CUSTOM_DUTY_INVALID, "%s spwd:dutyCycle %.2f", m_name, dutyCycle); | ||
56 | ✗ | return; | ||
57 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2187 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 2187 times.
|
2187 | } else if (dutyCycle < 0) { |
58 | ✗ | warning(ObdCode::CUSTOM_DUTY_TOO_LOW, "%s dutyCycle too low %.2f", m_name, dutyCycle); | ||
59 | ✗ | dutyCycle = 0; | ||
60 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2187 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 2187 times.
|
2187 | } else if (dutyCycle > 1) { |
61 | ✗ | warning(ObdCode::CUSTOM_PWM_DUTY_TOO_HIGH, "%s duty too high %.2f", m_name, dutyCycle); | ||
62 | ✗ | dutyCycle = 1; | ||
63 | } | |||
64 | ||||
65 | #if EFI_PROD_CODE | |||
66 | if (hardPwm) { | |||
67 | hardPwm->setDuty(dutyCycle); | |||
68 | return; | |||
69 | } | |||
70 | #endif | |||
71 | ||||
72 | // Handle near-zero and near-full duty cycle. This will cause the PWM output to behave like a plain digital output. | |||
73 |
2/2✓ Branch 0 taken 1756 times.
✓ Branch 1 taken 431 times.
|
2/2✓ Decision 'true' taken 1756 times.
✓ Decision 'false' taken 431 times.
|
2187 | if (dutyCycle < ZERO_PWM_THRESHOLD) { |
74 | 1756 | mode = PM_ZERO; | ||
75 | ||||
76 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1755 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 1755 times.
|
1756 | if (m_stateChangeCallback) { |
77 | // Manually fire falling edge | |||
78 | 1 | m_stateChangeCallback(0, this); | ||
79 | } | |||
80 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 430 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 430 times.
|
431 | } else if (dutyCycle > FULL_PWM_THRESHOLD) { |
81 | 1 | mode = PM_FULL; | ||
82 | ||||
83 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1 time.
|
1 | if (m_stateChangeCallback) { |
84 | // Manually fire rising edge | |||
85 | ✗ | m_stateChangeCallback(1, this); | ||
86 | } | |||
87 | } else { | |||
88 | 430 | mode = PM_NORMAL; | ||
89 | 430 | seq.setSwitchTime(0, dutyCycle); | ||
90 | } | |||
91 | } | |||
92 | ||||
93 | /** | |||
94 | * returns absolute timestamp of state change | |||
95 | */ | |||
96 | 15 | static efitick_t getNextSwitchTimeNt(PwmConfig *state) { | ||
97 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
|
15 | efiAssert(ObdCode::CUSTOM_ERR_ASSERT, state->safe.phaseIndex < PWM_PHASE_MAX_COUNT, "phaseIndex range", 0); | |
98 | 15 | int iteration = state->safe.iteration; | ||
99 | // we handle PM_ZERO and PM_FULL separately | |||
100 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 10 times.
|
15 | float switchTime = state->mode == PM_NORMAL ? state->multiChannelStateSequence->getSwitchTime(state->safe.phaseIndex) : 1; | |
101 | 15 | float periodNt = state->safe.periodNt; | ||
102 | #if DEBUG_PWM | |||
103 | efiPrintf("iteration=%d switchTime=%.2f period=%.2f", iteration, switchTime, period); | |||
104 | #endif /* DEBUG_PWM */ | |||
105 | ||||
106 | /** | |||
107 | * Once 'iteration' gets relatively high, we might lose calculation precision here. | |||
108 | * This is addressed by iterationLimit below, using any many cycles as possible without overflowing timeToSwitchNt | |||
109 | * Shall we reuse 'sumTickAndFloat' here? | |||
110 | */ | |||
111 | 15 | uint32_t timeToSwitchNt = (uint32_t)((iteration + switchTime) * periodNt); | ||
112 | ||||
113 | #if DEBUG_PWM | |||
114 | efiPrintf("start=%d timeToSwitch=%d", state->safe.start, timeToSwitch); | |||
115 | #endif /* DEBUG_PWM */ | |||
116 | 15 | return state->safe.startNt + timeToSwitchNt; | ||
117 | } | |||
118 | ||||
119 | 8 | void PwmConfig::setFrequency(float frequency) { | ||
120 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 6 times.
|
2/2✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 6 times.
|
8 | if (std::isnan(frequency)) { |
121 | // explicit code just to be sure | |||
122 | 2 | periodNt = NAN; | ||
123 | 2 | return; | ||
124 | } | |||
125 | /** | |||
126 | * see #handleCycleStart() | |||
127 | * 'periodNt' is below 10 seconds here so we use 32 bit type for performance reasons | |||
128 | */ | |||
129 | 6 | periodNt = USF2NT(frequency2periodUs(frequency)); | ||
130 | } | |||
131 | ||||
132 | ✗ | void PwmConfig::stop() { | ||
133 | ✗ | isStopRequested = true; | ||
134 | ✗ | } | ||
135 | ||||
136 | 13 | void PwmConfig::handleCycleStart() { | ||
137 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 13 times.
|
13 | if (safe.phaseIndex != 0) { |
138 | // https://github.com/rusefi/rusefi/issues/1030 | |||
139 | ✗ | firmwareError(ObdCode::CUSTOM_PWM_CYCLE_START, "handleCycleStart %d", safe.phaseIndex); | ||
140 | ✗ | return; | ||
141 | } | |||
142 | ||||
143 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 13 times.
|
13 | if (m_pwmCycleCallback) { |
144 | ✗ | m_pwmCycleCallback(this); | ||
145 | } | |||
146 | ||||
147 | // Compute the maximum number of iterations without overflowing a uint32_t worth of timestamp | |||
148 | 13 | uint32_t iterationLimitInt32 = (0xFFFFFFFF / periodNt) - 2; | ||
149 | ||||
150 | // Maximum number of iterations that don't lose precision due to 32b float (~7 decimal significant figures) | |||
151 | // We want at least 0.01% timing precision (aka 1/10000 cycle, 0.072 degree for trigger stimulator), which | |||
152 | // means we can't do any more than 2^23 / 10000 cycles = 838 iterations before a reset | |||
153 | 13 | uint32_t iterationLimitFloat = 838; | ||
154 | ||||
155 | 13 | uint32_t iterationLimit = minI(iterationLimitInt32, iterationLimitFloat); | ||
156 | ||||
157 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
|
13 | efiAssertVoid(ObdCode::CUSTOM_ERR_6580, periodNt != 0, "period not initialized"); | |
158 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
|
13 | efiAssertVoid(ObdCode::CUSTOM_ERR_6580, iterationLimit > 0, "iterationLimit invalid"); | |
159 |
4/6✓ Branch 0 taken 10 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 10 times.
|
2/2✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 10 times.
|
13 | if (forceCycleStart || safe.periodNt != periodNt || safe.iteration == iterationLimit) { |
160 | /** | |||
161 | * period length has changed - we need to reset internal state | |||
162 | */ | |||
163 | 3 | safe.startNt = getTimeNowNt(); | ||
164 | 3 | safe.iteration = 0; | ||
165 | 3 | safe.periodNt = periodNt; | ||
166 | ||||
167 | 3 | forceCycleStart = false; | ||
168 | #if DEBUG_PWM | |||
169 | efiPrintf("state reset start=%d iteration=%d", state->safe.start, state->safe.iteration); | |||
170 | #endif | |||
171 | } | |||
172 | } | |||
173 | ||||
174 | /** | |||
175 | * @return Next time for signal toggle | |||
176 | */ | |||
177 | 18 | efitick_t PwmConfig::togglePwmState() { | ||
178 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 18 times.
|
18 | if (isStopRequested) { |
179 | ✗ | return 0; | ||
180 | } | |||
181 | ||||
182 | #if DEBUG_PWM | |||
183 | efiPrintf("togglePwmState phaseIndex=%d iteration=%d", safe.phaseIndex, safe.iteration); | |||
184 | efiPrintf("period=%.2f safe.period=%.2f", period, safe.periodNt); | |||
185 | #endif | |||
186 | ||||
187 |
2/2✓ Branch 1 taken 3 times.
✓ Branch 2 taken 15 times.
|
2/2✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 15 times.
|
18 | if (std::isnan(periodNt)) { |
188 | // NaN period means PWM is paused, we also set the pin low | |||
189 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
1/2✓ Decision 'true' taken 3 times.
✗ Decision 'false' not taken.
|
3 | if (m_stateChangeCallback) { |
190 | 3 | m_stateChangeCallback(0, this); | ||
191 | } | |||
192 | ||||
193 | 3 | return getTimeNowNt() + MS2NT(NAN_FREQUENCY_SLEEP_PERIOD_MS); | ||
194 | } | |||
195 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 5 times.
|
2/2✓ Decision 'true' taken 10 times.
✓ Decision 'false' taken 5 times.
|
15 | if (mode != PM_NORMAL) { |
196 | // in case of ZERO or FULL we are always at starting index | |||
197 | 10 | safe.phaseIndex = 0; | ||
198 | } | |||
199 | ||||
200 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 2 times.
|
2/2✓ Decision 'true' taken 13 times.
✓ Decision 'false' taken 2 times.
|
15 | if (safe.phaseIndex == 0) { |
201 | 13 | handleCycleStart(); | ||
202 | } | |||
203 | ||||
204 | /** | |||
205 | * Here is where the 'business logic' - the actual pin state change is happening | |||
206 | */ | |||
207 | int cbStateIndex; | |||
208 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 10 times.
|
2/2✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 10 times.
|
15 | if (mode == PM_NORMAL) { |
209 | // callback state index is offset by one. todo: why? can we simplify this? | |||
210 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
|
5 | cbStateIndex = safe.phaseIndex == 0 ? multiChannelStateSequence->phaseCount - 1 : safe.phaseIndex - 1; | |
211 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 4 times.
|
2/2✓ Decision 'true' taken 6 times.
✓ Decision 'false' taken 4 times.
|
10 | } else if (mode == PM_ZERO) { |
212 | 6 | cbStateIndex = 0; | ||
213 | } else { | |||
214 | 4 | cbStateIndex = 1; | ||
215 | } | |||
216 | ||||
217 | { | |||
218 | 15 | ScopePerf perf(PE::PwmConfigStateChangeCallback); | ||
219 |
1/2✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
|
1/2✓ Decision 'true' taken 15 times.
✗ Decision 'false' not taken.
|
15 | if (m_stateChangeCallback) { |
220 |
1/1✓ Branch 1 taken 15 times.
|
15 | m_stateChangeCallback(cbStateIndex, this); | |
221 | } | |||
222 | } | |||
223 | ||||
224 | 15 | efitick_t nextSwitchTimeNt = getNextSwitchTimeNt(this); | ||
225 | #if DEBUG_PWM | |||
226 | efiPrintf("%s: nextSwitchTime %d", state->m_name, nextSwitchTime); | |||
227 | #endif /* DEBUG_PWM */ | |||
228 | ||||
229 | // If we're very far behind schedule, restart the cycle fresh to avoid scheduling a huge pile of events all at once | |||
230 | // This can happen during config write or debugging where CPU is halted for multiple seconds | |||
231 | 15 | bool isVeryBehindSchedule = nextSwitchTimeNt < getTimeNowNt() - MS2NT(10); | ||
232 | ||||
233 | 15 | safe.phaseIndex++; | ||
234 |
5/6✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 10 times.
✓ Branch 5 taken 3 times.
|
2/2✓ Decision 'true' taken 12 times.
✓ Decision 'false' taken 3 times.
|
15 | if (isVeryBehindSchedule || safe.phaseIndex == multiChannelStateSequence->phaseCount || mode != PM_NORMAL) { |
235 | 12 | safe.phaseIndex = 0; // restart | ||
236 | 12 | safe.iteration++; | ||
237 | ||||
238 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 12 times.
|
12 | if (isVeryBehindSchedule) { |
239 | ✗ | forceCycleStart = true; | ||
240 | } | |||
241 | } | |||
242 | #if EFI_UNIT_TEST | |||
243 | 15 | printf("PWM: nextSwitchTimeNt=%d phaseIndex=%d iteration=%d\r\n", nextSwitchTimeNt, | ||
244 | safe.phaseIndex, | |||
245 | safe.iteration); | |||
246 | #endif /* EFI_UNIT_TEST */ | |||
247 | 15 | return nextSwitchTimeNt; | ||
248 | } | |||
249 | ||||
250 | /** | |||
251 | * Main PWM loop: toggle pin & schedule next invocation | |||
252 | * | |||
253 | * First invocation happens on application thread | |||
254 | */ | |||
255 | 18 | static void timerCallback(PwmConfig *state) { | ||
256 | 18 | ScopePerf perf(PE::PwmGeneratorCallback); | ||
257 | ||||
258 | 18 | state->dbgNestingLevel++; | ||
259 |
1/3✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
✗ Branch 3 not taken.
|
18 | efiAssertVoid(ObdCode::CUSTOM_ERR_6581, state->dbgNestingLevel < 25, "PWM nesting issue"); | |
260 | ||||
261 |
1/1✓ Branch 1 taken 18 times.
|
18 | efitick_t switchTimeNt = state->togglePwmState(); | |
262 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 18 times.
|
18 | if (switchTimeNt == 0) { |
263 | // we are here when PWM gets stopped | |||
264 | ✗ | return; | ||
265 | } | |||
266 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 18 times.
|
18 | if (state->m_executor == nullptr) { |
267 | ✗ | firmwareError(ObdCode::CUSTOM_NULL_EXECUTOR, "exec on %s", state->m_name); | ||
268 | ✗ | return; | ||
269 | } | |||
270 | ||||
271 |
1/1✓ Branch 3 taken 18 times.
|
18 | state->m_executor->schedule(state->m_name, &state->scheduling, switchTimeNt, action_s::make<timerCallback>( state )); | |
272 | 18 | state->dbgNestingLevel--; | ||
273 | } | |||
274 | ||||
275 | /** | |||
276 | * Incoming parameters are potentially just values on current stack, so we have to copy | |||
277 | * into our own permanent storage, right? | |||
278 | */ | |||
279 | 4 | void copyPwmParameters(PwmConfig *state, MultiChannelStateSequence const * seq) { | ||
280 | 4 | state->multiChannelStateSequence = seq; | ||
281 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
|
2/2✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 1 time.
|
4 | if (state->mode == PM_NORMAL) { |
282 | 3 | state->multiChannelStateSequence->checkSwitchTimes(1); | ||
283 | } | |||
284 | 4 | } | ||
285 | ||||
286 | /** | |||
287 | * this method also starts the timer cycle | |||
288 | * See also startSimplePwm | |||
289 | */ | |||
290 | 4 | void PwmConfig::weComplexInit(Scheduler *executor, | ||
291 | MultiChannelStateSequence const * seq, | |||
292 | pwm_cycle_callback *pwmCycleCallback, pwm_gen_callback *stateChangeCallback) { | |||
293 | 4 | m_executor = executor; | ||
294 | 4 | isStopRequested = false; | ||
295 | ||||
296 | // NaN is 'not initialized' but zero is not expected | |||
297 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | criticalAssertVoid(periodNt != 0, "period is not initialized"); | |
298 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | criticalAssertVoid(seq->phaseCount != 0, "signal length cannot be zero"); | |
299 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | criticalAssertVoid(seq->phaseCount <= PWM_PHASE_MAX_COUNT, "too many phases in PWM"); | |
300 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | criticalAssertVoid(seq->waveCount > 0, "waveCount should be positive"); | |
301 | ||||
302 | 4 | m_pwmCycleCallback = pwmCycleCallback; | ||
303 | 4 | m_stateChangeCallback = stateChangeCallback; | ||
304 | ||||
305 | 4 | copyPwmParameters(this, seq); | ||
306 | ||||
307 | 4 | safe.phaseIndex = 0; | ||
308 | 4 | safe.periodNt = -1; | ||
309 | 4 | safe.iteration = -1; | ||
310 | ||||
311 | // let's start the indefinite callback loop of PWM generation | |||
312 | 4 | timerCallback(this); | ||
313 | } | |||
314 | ||||
315 | 4 | void startSimplePwm(SimplePwm *state, const char *msg, | ||
316 | Scheduler *executor, | |||
317 | OutputPin *output, float frequency, float dutyCycle, pwm_gen_callback *callback) { | |||
318 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | efiAssertVoid(ObdCode::CUSTOM_ERR_PWM_STATE_ASSERT, state != NULL, "state"); | |
319 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
|
4 | efiAssertVoid(ObdCode::CUSTOM_ERR_PWM_DUTY_ASSERT, dutyCycle >= 0 && dutyCycle <= PWM_MAX_DUTY, "dutyCycle"); | |
320 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 4 times.
|
4 | if (frequency < 1) { |
321 | ✗ | warning(ObdCode::CUSTOM_OBD_LOW_FREQUENCY, "low frequency %.2f %s", frequency, msg); | ||
322 | ✗ | return; | ||
323 | } | |||
324 | ||||
325 | #if EFI_PROD_CODE | |||
326 | #if (BOARD_EXT_GPIOCHIPS > 0) | |||
327 | if (!callback) { | |||
328 | /* No specific scheduler, we can try enabling HW PWM */ | |||
329 | if (brain_pin_is_ext(output->brainPin)) { | |||
330 | /* this pin is driven by external gpio chip, let's see if it can PWM */ | |||
331 | state->hardPwm = gpiochip_tryInitPwm(msg, output->brainPin, frequency, dutyCycle); | |||
332 | } | |||
333 | /* TODO: sohuld we try to init MCU PWM on on-chip brainPin? | |||
334 | * Or this should be done only on startSimplePwmHard() call? */ | |||
335 | } | |||
336 | ||||
337 | /* We have succesufully started HW PWM on this output, no need to continue with SW */ | |||
338 | if (state->hardPwm) { | |||
339 | return; | |||
340 | } | |||
341 | #endif | |||
342 | #endif | |||
343 | ||||
344 | /* Set default executor for SW PWM */ | |||
345 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
1/2✓ Decision 'true' taken 4 times.
✗ Decision 'false' not taken.
|
4 | if (!callback) { |
346 | 4 | callback = applyPinState; | ||
347 | } | |||
348 | ||||
349 | 4 | state->seq.setSwitchTime(0, dutyCycle); | ||
350 | 4 | state->seq.setSwitchTime(1, PWM_MAX_DUTY); | ||
351 | 4 | state->seq.setChannelState(0, 0, TriggerValue::FALL); | ||
352 | 4 | state->seq.setChannelState(0, PWM_MAX_DUTY, TriggerValue::RISE); | ||
353 | ||||
354 | 4 | state->outputPins[0] = output; | ||
355 | ||||
356 | 4 | state->setFrequency(frequency); | ||
357 | 4 | state->setSimplePwmDutyCycle(dutyCycle); | ||
358 | 4 | state->weComplexInit(executor, &state->seq, nullptr, callback); | ||
359 | } | |||
360 | ||||
361 | ✗ | void startSimplePwmExt(SimplePwm *state, const char *msg, | ||
362 | Scheduler *executor, | |||
363 | brain_pin_e brainPin, OutputPin *output, float frequency, | |||
364 | float dutyCycle, pwm_gen_callback *callback) { | |||
365 | ||||
366 | ✗ | output->initPin(msg, brainPin); | ||
367 | ||||
368 | ✗ | startSimplePwm(state, msg, executor, output, frequency, dutyCycle, callback); | ||
369 | ✗ | } | ||
370 | ||||
371 | /** | |||
372 | * @param dutyCycle value between 0 and 1 | |||
373 | */ | |||
374 | ✗ | void startSimplePwmHard(SimplePwm *state, const char *msg, | ||
375 | Scheduler *executor, | |||
376 | brain_pin_e brainPin, OutputPin *output, float frequency, | |||
377 | float dutyCycle) { | |||
378 | #if EFI_PROD_CODE && HAL_USE_PWM | |||
379 | auto hardPwm = hardware_pwm::tryInitPin(msg, brainPin, frequency, dutyCycle); | |||
380 | ||||
381 | if (hardPwm) { | |||
382 | state->hardPwm = hardPwm; | |||
383 | } else { | |||
384 | #endif | |||
385 | ✗ | startSimplePwmExt(state, msg, executor, brainPin, output, frequency, dutyCycle); | ||
386 | #if EFI_PROD_CODE && HAL_USE_PWM | |||
387 | } | |||
388 | #endif | |||
389 | ✗ | } | ||
390 | ||||
391 | /** | |||
392 | * default implementation of pwm_gen_callback which simply toggles the pins | |||
393 | * | |||
394 | */ | |||
395 | 19 | void PwmConfig::applyPwmValue(OutputPin *output, int stateIndex, /* weird argument order to facilitate default parameter value */int channelIndex) { | ||
396 | 19 | TriggerValue value = multiChannelStateSequence->getChannelState(channelIndex, stateIndex); | ||
397 | 19 | output->setValue(value == TriggerValue::RISE); | ||
398 | 19 | } | ||
399 | ||||
400 | /** | |||
401 | * This method controls the actual hardware pins | |||
402 | */ | |||
403 | 19 | void applyPinState(int stateIndex, PwmConfig *state) /* pwm_gen_callback */ { | ||
404 | #if EFI_PROD_CODE | |||
405 | if (!engine->isPwmEnabled) { | |||
406 | for (int channelIndex = 0; channelIndex < state->multiChannelStateSequence->waveCount; channelIndex++) { | |||
407 | OutputPin *output = state->outputPins[channelIndex]; | |||
408 | output->setValue(0); | |||
409 | } | |||
410 | return; | |||
411 | } | |||
412 | #endif // EFI_PROD_CODE | |||
413 | ||||
414 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
|
19 | efiAssertVoid(ObdCode::CUSTOM_ERR_6663, stateIndex < PWM_PHASE_MAX_COUNT, "invalid stateIndex"); | |
415 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
|
19 | efiAssertVoid(ObdCode::CUSTOM_ERR_6664, state->multiChannelStateSequence->waveCount <= PWM_PHASE_MAX_WAVE_PER_PWM, "invalid waveCount"); | |
416 |
2/2✓ Branch 0 taken 19 times.
✓ Branch 1 taken 19 times.
|
2/2✓ Decision 'true' taken 19 times.
✓ Decision 'false' taken 19 times.
|
38 | for (int channelIndex = 0; channelIndex < state->multiChannelStateSequence->waveCount; channelIndex++) { |
417 | 19 | OutputPin *output = state->outputPins[channelIndex]; | ||
418 | 19 | state->applyPwmValue(output, stateIndex, channelIndex); | ||
419 | } | |||
420 | } | |||
421 |