| 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 | 2144 | void SimplePwm::setSimplePwmDutyCycle(float dutyCycle) { | ||
| 50 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2144 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 2144 times.
|
2144 | 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 2144 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 2144 times.
|
2144 | 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 2144 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 2144 times.
|
2144 | } 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 2144 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 2144 times.
|
2144 | } 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 1708 times.
✓ Branch 1 taken 436 times.
|
2/2✓ Decision 'true' taken 1708 times.
✓ Decision 'false' taken 436 times.
|
2144 | if (dutyCycle < ZERO_PWM_THRESHOLD) { |
| 74 | 1708 | mode = PM_ZERO; | ||
| 75 | ||||
| 76 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1707 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 1707 times.
|
1708 | 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 435 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 435 times.
|
436 | } 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 | 435 | mode = PM_NORMAL; | ||
| 89 | 435 | 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 |