rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
pwm_generator_logic.cpp
Go to the documentation of this file.
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
29
30SimplePwm::SimplePwm(const char *name) : SimplePwm() {
31 m_name = name;
32}
33
35 memset((void*)&scheduling, 0, sizeof(scheduling));
36 memset((void*)&safe, 0, sizeof(safe));
38 periodNt = NAN;
40 memset(&outputPins, 0, sizeof(outputPins));
41 m_name = "[noname]";
42}
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 */
49void SimplePwm::setSimplePwmDutyCycle(float dutyCycle) {
50 if (isStopRequested) {
51 // we are here in order to not change pin once PWM stop was requested
52 return;
53 }
54 if (std::isnan(dutyCycle)) {
55 warning(ObdCode::CUSTOM_DUTY_INVALID, "%s spwd:dutyCycle %.2f", m_name, dutyCycle);
56 return;
57 } else if (dutyCycle < 0) {
58 warning(ObdCode::CUSTOM_DUTY_TOO_LOW, "%s dutyCycle too low %.2f", m_name, dutyCycle);
59 dutyCycle = 0;
60 } 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 if (dutyCycle < ZERO_PWM_THRESHOLD) {
74 mode = PM_ZERO;
75
77 // Manually fire falling edge
78 m_stateChangeCallback(0, this);
79 }
80 } else if (dutyCycle > FULL_PWM_THRESHOLD) {
81 mode = PM_FULL;
82
84 // Manually fire rising edge
85 m_stateChangeCallback(1, this);
86 }
87 } else {
89 seq.setSwitchTime(0, dutyCycle);
90 }
91}
92
93/**
94 * returns absolute timestamp of state change
95 */
97 efiAssert(ObdCode::CUSTOM_ERR_ASSERT, state->safe.phaseIndex < PWM_PHASE_MAX_COUNT, "phaseIndex range", 0);
98 int iteration = state->safe.iteration;
99 // we handle PM_ZERO and PM_FULL separately
100 float switchTime = state->mode == PM_NORMAL ? state->multiChannelStateSequence->getSwitchTime(state->safe.phaseIndex) : 1;
101 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 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 return state->safe.startNt + timeToSwitchNt;
117}
118
120 if (std::isnan(frequency)) {
121 // explicit code just to be sure
122 periodNt = NAN;
123 return;
124 }
125 /**
126 * see #handleCycleStart()
127 * 'periodNt' is below 10 seconds here so we use 32 bit type for performance reasons
128 */
129 periodNt = USF2NT(frequency2periodUs(frequency));
130}
131
133 isStopRequested = true;
134}
135
137 if (safe.phaseIndex != 0) {
138 // https://github.com/rusefi/rusefi/issues/1030
140 return;
141 }
142
143 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 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 uint32_t iterationLimitFloat = 838;
154
155 uint32_t iterationLimit = minI(iterationLimitInt32, iterationLimitFloat);
156
157 efiAssertVoid(ObdCode::CUSTOM_ERR_6580, periodNt != 0, "period not initialized");
158 efiAssertVoid(ObdCode::CUSTOM_ERR_6580, iterationLimit > 0, "iterationLimit invalid");
159 if (forceCycleStart || safe.periodNt != periodNt || safe.iteration == iterationLimit) {
160 /**
161 * period length has changed - we need to reset internal state
162 */
164 safe.iteration = 0;
166
167 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 */
178 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 if (std::isnan(periodNt)) {
188 // NaN period means PWM is paused, we also set the pin low
190 m_stateChangeCallback(0, this);
191 }
192
193 return getTimeNowNt() + MS2NT(NAN_FREQUENCY_SLEEP_PERIOD_MS);
194 }
195 if (mode != PM_NORMAL) {
196 // in case of ZERO or FULL we are always at starting index
197 safe.phaseIndex = 0;
198 }
199
200 if (safe.phaseIndex == 0) {
202 }
203
204 /**
205 * Here is where the 'business logic' - the actual pin state change is happening
206 */
207 int cbStateIndex;
208 if (mode == PM_NORMAL) {
209 // callback state index is offset by one. todo: why? can we simplify this?
210 cbStateIndex = safe.phaseIndex == 0 ? multiChannelStateSequence->phaseCount - 1 : safe.phaseIndex - 1;
211 } else if (mode == PM_ZERO) {
212 cbStateIndex = 0;
213 } else {
214 cbStateIndex = 1;
215 }
216
217 {
220 m_stateChangeCallback(cbStateIndex, this);
221 }
222 }
223
224 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 bool isVeryBehindSchedule = nextSwitchTimeNt < getTimeNowNt() - MS2NT(10);
232
234 if (isVeryBehindSchedule || safe.phaseIndex == multiChannelStateSequence->phaseCount || mode != PM_NORMAL) {
235 safe.phaseIndex = 0; // restart
236 safe.iteration++;
237
238 if (isVeryBehindSchedule) {
239 forceCycleStart = true;
240 }
241 }
242#if EFI_UNIT_TEST
243 printf("PWM: nextSwitchTimeNt=%d phaseIndex=%d iteration=%d\r\n", nextSwitchTimeNt,
246#endif /* EFI_UNIT_TEST */
247 return nextSwitchTimeNt;
248}
249
250/**
251 * Main PWM loop: toggle pin & schedule next invocation
252 *
253 * First invocation happens on application thread
254 */
257
258 state->dbgNestingLevel++;
259 efiAssertVoid(ObdCode::CUSTOM_ERR_6581, state->dbgNestingLevel < 25, "PWM nesting issue");
260
261 efitick_t switchTimeNt = state->togglePwmState();
262 if (switchTimeNt == 0) {
263 // we are here when PWM gets stopped
264 return;
265 }
266 if (state->m_executor == nullptr) {
267 firmwareError(ObdCode::CUSTOM_NULL_EXECUTOR, "exec on %s", state->m_name);
268 return;
269 }
270
271 state->m_executor->schedule(state->m_name, &state->scheduling, switchTimeNt, action_s::make<timerCallback>( state ));
272 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 */
280 state->multiChannelStateSequence = seq;
281 if (state->mode == PM_NORMAL) {
282 state->multiChannelStateSequence->checkSwitchTimes(1);
283 }
284}
285
286/**
287 * this method also starts the timer cycle
288 * See also startSimplePwm
289 */
291 MultiChannelStateSequence const * seq,
292 pwm_cycle_callback *pwmCycleCallback, pwm_gen_callback *stateChangeCallback) {
293 m_executor = executor;
294 isStopRequested = false;
295
296 // NaN is 'not initialized' but zero is not expected
297 criticalAssertVoid(periodNt != 0, "period is not initialized");
298 criticalAssertVoid(seq->phaseCount != 0, "signal length cannot be zero");
299 criticalAssertVoid(seq->phaseCount <= PWM_PHASE_MAX_COUNT, "too many phases in PWM");
300 criticalAssertVoid(seq->waveCount > 0, "waveCount should be positive");
301
302 m_pwmCycleCallback = pwmCycleCallback;
303 m_stateChangeCallback = stateChangeCallback;
304
305 copyPwmParameters(this, seq);
306
307 safe.phaseIndex = 0;
308 safe.periodNt = -1;
309 safe.iteration = -1;
310
311 // let's start the indefinite callback loop of PWM generation
312 timerCallback(this);
313}
314
315void startSimplePwm(SimplePwm *state, const char *msg,
316 Scheduler *executor,
317 OutputPin *output, float frequency, float dutyCycle, pwm_gen_callback *callback) {
318 efiAssertVoid(ObdCode::CUSTOM_ERR_PWM_STATE_ASSERT, state != NULL, "state");
319 efiAssertVoid(ObdCode::CUSTOM_ERR_PWM_DUTY_ASSERT, dutyCycle >= 0 && dutyCycle <= PWM_MAX_DUTY, "dutyCycle");
320 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 if (!callback) {
346 callback = applyPinState;
347 }
348
349 state->seq.setSwitchTime(0, dutyCycle);
350 state->seq.setSwitchTime(1, PWM_MAX_DUTY);
351 state->seq.setChannelState(0, 0, TriggerValue::FALL);
352 state->seq.setChannelState(0, PWM_MAX_DUTY, TriggerValue::RISE);
353
354 state->outputPins[0] = output;
355
356 state->setFrequency(frequency);
357 state->setSimplePwmDutyCycle(dutyCycle);
358 state->weComplexInit(executor, &state->seq, nullptr, callback);
359}
360
361void 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 */
374void 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 */
395void PwmConfig::applyPwmValue(OutputPin *output, int stateIndex, /* weird argument order to facilitate default parameter value */int channelIndex) {
396 TriggerValue value = multiChannelStateSequence->getChannelState(channelIndex, stateIndex);
397 output->setValue(value == TriggerValue::RISE);
398}
399
400/**
401 * This method controls the actual hardware pins
402 */
403void 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 efiAssertVoid(ObdCode::CUSTOM_ERR_6663, stateIndex < PWM_PHASE_MAX_COUNT, "invalid stateIndex");
415 efiAssertVoid(ObdCode::CUSTOM_ERR_6664, state->multiChannelStateSequence->waveCount <= PWM_PHASE_MAX_WAVE_PER_PWM, "invalid waveCount");
416 for (int channelIndex = 0; channelIndex < state->multiChannelStateSequence->waveCount; channelIndex++) {
417 OutputPin *output = state->outputPins[channelIndex];
418 state->applyPwmValue(output, stateIndex, channelIndex);
419 }
420}
beuint32_t period
bool isPwmEnabled
Definition engine.h:117
void checkSwitchTimes(float scale) const
virtual pin_state_t getChannelState(int channelIndex, int phaseIndex) const =0
void setSwitchTime(const int phaseIndex, const float value)
Single output pin reference and state.
Definition efi_output.h:49
void initPin(const char *msg, brain_pin_e brainPin, pin_output_mode_e outputMode, bool forceInitWithFatalError=false)
Definition efi_gpio.cpp:711
void setValue(const char *msg, int logicValue, bool isForce=false)
Definition efi_gpio.cpp:604
brain_pin_e brainPin
Definition efi_output.h:86
Multi-channel software PWM output configuration.
pwm_mode_e mode
pwm_gen_callback * m_stateChangeCallback
Scheduler * m_executor
OutputPin * outputPins[PWM_PHASE_MAX_WAVE_PER_PWM]
scheduling_s scheduling
void weComplexInit(Scheduler *executor, MultiChannelStateSequence const *seq, pwm_cycle_callback *pwmCycleCallback, pwm_gen_callback *callback)
MultiChannelStateSequence const * multiChannelStateSequence
efitick_t togglePwmState()
void setFrequency(float frequency)
const char * m_name
pwm_config_safe_state_s safe
pwm_cycle_callback * m_pwmCycleCallback
void applyPwmValue(OutputPin *output, int stateIndex, int channelIndex=0)
hardware_pwm * hardPwm
void setSimplePwmDutyCycle(float dutyCycle) override
MultiChannelStateSequenceWithData< 2 > seq
hardware_pwm * gpiochip_tryInitPwm(const char *msg, brain_pin_e pin, float frequency, float duty)
Try to init PWM on given pin.
Definition core.cpp:439
efitick_t getTimeNowNt()
Definition efitime.cpp:19
static EngineAccessor engine
Definition engine.h:413
bool warning(ObdCode code, const char *fmt,...)
void firmwareError(ObdCode code, const char *fmt,...)
static float frequency
Definition init_flex.cpp:21
@ CUSTOM_ERR_PWM_DUTY_ASSERT
@ CUSTOM_DUTY_INVALID
@ CUSTOM_ERR_6664
@ CUSTOM_OBD_LOW_FREQUENCY
@ CUSTOM_NULL_EXECUTOR
@ CUSTOM_ERR_6663
@ CUSTOM_ERR_6581
@ CUSTOM_DUTY_TOO_LOW
@ CUSTOM_PWM_DUTY_TOO_HIGH
@ CUSTOM_ERR_ASSERT
@ CUSTOM_ERR_PWM_STATE_ASSERT
@ CUSTOM_PWM_CYCLE_START
@ CUSTOM_ERR_6580
@ PwmGeneratorCallback
@ PwmConfigStateChangeCallback
bool brain_pin_is_ext(brain_pin_e brainPin)
void startSimplePwmExt(SimplePwm *state, const char *msg, Scheduler *executor, brain_pin_e brainPin, OutputPin *output, float frequency, float dutyCycle, pwm_gen_callback *callback)
void applyPinState(int stateIndex, PwmConfig *state)
void startSimplePwmHard(SimplePwm *state, const char *msg, Scheduler *executor, brain_pin_e brainPin, OutputPin *output, float frequency, float dutyCycle)
static void timerCallback(PwmConfig *state)
void startSimplePwm(SimplePwm *state, const char *msg, Scheduler *executor, OutputPin *output, float frequency, float dutyCycle, pwm_gen_callback *callback)
void copyPwmParameters(PwmConfig *state, MultiChannelStateSequence const *seq)
static efitick_t getNextSwitchTimeNt(PwmConfig *state)
void() pwm_gen_callback(int stateIndex, PwmConfig *pwm)
void() pwm_cycle_callback(PwmConfig *state)
@ PM_NORMAL
void copyPwmParameters(PwmConfig *state, MultiChannelStateSequence const *seq)
state("state", SensorCategory.SENSOR_INPUTS, FieldType.INT8, 1871, 1.0, -1.0, -1.0, "")
TriggerValue
static hardware_pwm * tryInitPin(const char *msg, brain_pin_e pin, float frequencyHz, float duty)
Definition mpu_util.cpp:257
virtual void setDuty(float duty)=0
printf("\n")