-
Notifications
You must be signed in to change notification settings - Fork 513
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
P2 PWM fixture tests. PWM HAL fixes #2511
Conversation
pwmInfo[instance].resolution[channel] = resolution; | ||
pwmInfo[instance].values[channel] = ((float)pwmInfo[instance].values[channel] / prePeriodCycles) * newPeriodCycles; | ||
pwmInfo[instance].channels[channel].resolution = resolution; | ||
pwmInfo[instance].channels[channel].value = ((float)pwmInfo[instance].channels[channel].value / prePeriodCycles) * newPeriodCycles; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a new resolution is set, should we recalculate the ARR and CCMRX register values (ie duty cycle) to update the new values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a pin is already running a non-zero PWM output, and the resolution is changed... we don't have docs that say what is expected to happen. I don't think the duty cycle value should be re-adjusted though (that's up to the user), but the frequency probably should remain the same (if it already doesn't). We probably should have a note that says something like, "It's recommended to set the resolution before calculating the duty cycle value and setting the PWM to a non-zero value." ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will store the new resolution and duty cycle value in the PWM HAL, but it wont be applied to the timer registers and the PWM output duty cycle / frequency wont change.
The user will have to call analogWrite()
to get their duty cycle/frequency changes to take effect. So there is no new behavior here, but yeah could be documented better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will store the new resolution and duty cycle value in the PWM HAL, but it wont be applied to the timer registers and the PWM output duty cycle / frequency wont change.
The user will have to call analogWrite() to get their duty cycle/frequency changes to take effect. So there is no new behavior here, but yeah could be documented better?
That's the expected behavior.
const uint32_t rtlPin = hal_pin_to_rtl_pin(pin); | ||
Pinmux_Config(rtlPin, PINMUX_FUNCTION_GPIO); | ||
|
||
pwmInfo[instance].pins[channel] = PIN_INVALID; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting the pin to PIN_INVALID
made it impossible to re-generate the PWM signal on the suspended PWM channel when hal_pwm_sleep(true) / hal_pwm_sleep(false)
was called. Thats why I changed pwm hal to track each channel state. When a pin is init'd, the pin number will always be preserved
|
||
// If the calculated ARR value is larger than will fit in the 16 bit ARR register | ||
// Increase the prescaler and recalculate until we get a value that fits | ||
while (*arr > ((1 << RTL_PWM_TIM_MAX_RESOLUTION) - 1) && (calculated_prescaler < MAX_PRESCALER-1)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason why we need to change the algorithm? The loop will take longer to figure out the desired ARR
andd prescaler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it was because of a bug found with lower resolutions. As a compromise can we pre-calculate things, and then drop into the while() loop to make a quick tweak, instead of starting from calculated_prescaler = 0
every time? I'm sure this is pretty fast already, and also I guess this does get called on every call to analogWrite* so the time it takes could be important to some.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the calculated arr
value was larger than 16 bits, it was truncated at 16 bits.
Then the truncated value was used to calculate the prescaler value. This would lead to incorrect PWM frequencies, especially at low frequencies.
For example, the default PWM frequency is 500hz. that means arr
is calculated to be:
40mhz / 500 hz = 80000
which is greater than 1<<16 -1
(ie 0xFFFF) and is truncated to 0xFFFF
When using the truncated value to calculate prescaler:
40mhz / 500 hz / 0xFFFF = 1.22
1.22 + 0.5 = 1.72
Casting to uint8_t
rounds to 1
. Subtracting 1 from that means the prescaler should be 0.
arr
set to 0xFFFF
and prescaler 0
gives a frequency calculation of:
40mhz / 0xFFFF = 610 hz
Which is not the desired 500hz output.
This method is somewhat slower but should be more accurate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes perfect sense. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we maybe cache the frequency value and only re-calculate if the frequency for the pin changes? This seems like something that only needs to be calculated once when the frequency is changed. Usually you setup a PWM to be a certain resolution/frequency and then only vary the duty cycle after that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Determining the prescaler is only done when changing the frequency. Do we think this will be done frequently enough to need to improve performance here?
Modifying duty cycle via hal_pwm_update_duty_cycle_ext()
only updates the CCR register, which should be fast.
I would prefer to not make this more complicated unless needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looked to me that calculateArrAndPrescaler
was called from every call to hal_pwm_write_with_frequency_ext
which also gets called by hal_pwm_write*
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests passed several times for me 👍🏼
After the 3rd test run I noticed this failure:
Assertion failed: (avgPulseHigh=342) <= (316=316), file tests/wiring/pwm/pwm.cpp, line 107.
Test PWM_01_LowDCAnalogWriteOnPinResultsInCorrectPulseWidth failed.
So I ran it 100 more times in a loop, only failed 3 times:
Assertion failed: (avgPulseHigh=283) <= (250=250), file tests/wiring/pwm/pwm.cpp, line 119.
Assertion failed: (avgPulseHigh=278) <= (250=250), file tests/wiring/pwm/pwm.cpp, line 119.
Assertion failed: (avgPulseHigh=300) <= (250=250), file tests/wiring/pwm/pwm.cpp, line 119.
Would it be ok to increase the tolerance here and just check for really blatant errors?
|
||
// If the calculated ARR value is larger than will fit in the 16 bit ARR register | ||
// Increase the prescaler and recalculate until we get a value that fits | ||
while (*arr > ((1 << RTL_PWM_TIM_MAX_RESOLUTION) - 1) && (calculated_prescaler < MAX_PRESCALER-1)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it was because of a bug found with lower resolutions. As a compromise can we pre-calculate things, and then drop into the while() loop to make a quick tweak, instead of starting from calculated_prescaler = 0
every time? I'm sure this is pretty fast already, and also I guess this does get called on every call to analogWrite* so the time it takes could be important to some.
Since this test is manual I think its probably fine to leave as is and just accept that a tight tolerance means some false failures? I dont really know how the margins of error were originally determined anyways. |
Problem
P2 cannot read a PWM pin itself using
pulse_in()
. This PR adds a PWM fixture test that uses one pin to generate a PWM output and another to measure it viapulse_in()
.Solution
Port the
no_fixture_long_running/pwm
tests into awiring/pwm
fixture test app.pulse_in
is used to measure the output pins signal. Each permutation of output/input pins is configured and run for each test.Implementing these tests resulted in some PWM hal fixes/changes:
hal_pwm_set_resolution()
the register values are calculated to match the new "resolution".)pwmInfo
state structure refactored a bit. Each channel has its own state to make pwm sleep/wake easierpinMode()
will disable PWM configuration from pin.pwmPinDeinit()
will not clear pin from pwmInfo state, allowinghal_pwm_sleep()
to work as intendedSteps to Test
make PLATFORM=p2 TEST=wiring/pwm
t
to start all testsExample App
use
wiring/pwm
Test output (verified with logic analyer)
References
Completeness