-
Notifications
You must be signed in to change notification settings - Fork 973
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
HardwareTimer: rework internal mapping of arduino API to HAL/LL API #806
Conversation
a170fe8
to
e8b3300
Compare
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.
There is a regression on tone.
Main rework: * HAL_TIM_Base_Init() is now called only once at object creation * HAL_TIM_xxx_ConfigChannel is now done in setMode() * HAL_TIM_xxx_Start is done in resumeChannel() * use LL when possible * Configuration are directly made through hardware register access (xhen possible), then remove useless attribut * Add new API to pause only one channel: pauseChannel(uint32_t channel) resumeChannel(uint32_t channel) fixes stm32duino#763 * integration of PR stm32duino#767 Flexible interrupts handling
b2c1548
to
d8f79dd
Compare
When timerTonePinDeinit() is called, it will deinitialize timer. It is then necessary to recreate HardwareTimer object to initialize timer. So, when NoTone() is called, without destruct, only pause the timer. Also when frequency is null just pause the timer.
d8f79dd
to
80dfa09
Compare
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'm a bit late to the party, but had a look at the code anyway. Overall, the changes look great, stuff is cleaner and more predictable. I left some small comments inline, mostly regarding the interrupt handling.
Regarding the interrupt enabling and disabling, which currently happens in both resume()
/pause()
and in attachInterrupt()
/detachtInterrupt()
(similarly for the channel interrupts).
I wonder:
- Since interrupts are enabled in
attachInterrupt
, which might occur beforestart()
, they may be enabled before the timer is running. I assume this is not problematic, since without a running timer, the interrupt would simply not trigger. If so, is it still needed to disable the interrupts inpause()
, would it not be sufficient to disable the timer? - If
pause()
does not disable interrupts,resume()
does not need to enable them (they will be exclusively enabled/disabled byattach/detachInterrupt()
then. - For channels, the above does not hold AFAICS (since
pauseChannel()
does not halt the timer and disabling the channel only prevents input capture and output but still allows interrupts, so it must disable the interrupt to prevent it from triggering, I believe). This would probably mean thatresumeChannel()
/pauseChannel()
should still enable/disable the interrupt, but that also means thatattachInterrupt()
should not enable the interrupt if the channel is not currently running. - The same could again be applied to the timer interrupt - only enable in
attachInterrupt
if the timer is actually running.
Overall, it might be interesting to think about or document how the various functions are expected to behave. E.g. things like what happens when you pause a channel when the timer is not running? What happens when you resume the timer then? What happens when you attach an interrupt while the timer is running? Etc. Also, are all relevant usecases covered?
__HAL_TIM_CLEAR_FLAG(&(_timerObj.handle), TIM_FLAG_UPDATE); | ||
// Enable update interrupt only if callback is valid | ||
__HAL_TIM_ENABLE_IT(&(_timerObj.handle), TIM_IT_UPDATE); | ||
} | ||
callbacks[0] = callback; |
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 there is a race condition here, if the interrupt immediately triggers before callbacks[0]
is set. Setting it before enabling interrupts would help, I think.
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.
Right, I will take it into account.
__HAL_TIM_CLEAR_FLAG(&(_timerObj.handle), interrupt); | ||
// Enable interrupt corresponding to channel, only if callback is valid | ||
__HAL_TIM_ENABLE_IT(&(_timerObj.handle), interrupt); | ||
} | ||
callbacks[channel] = callback; |
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.
Same race condition.
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.
Right, I will take it into account.
if ((channel == 0) || (channel > (TIMER_CHANNELS + 1))) { | ||
Error_Handler(); // only channel 1..4 have an interrupt | ||
} | ||
|
||
if (callback != NULL) { | ||
// Clear flag before enabling IT | ||
__HAL_TIM_CLEAR_FLAG(&(_timerObj.handle), interrupt); |
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 believe that when the interrupt is already enabled (e.g. when you are changing the interrupt handler to a different one), this flag clear is not needed (and even harmful, since it might cause one interrupt to be skipped, I think).
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 understand what you said, but Idisagree:
In the case you change the callback while timer is running, you will not loose interrupt, because if IT is pending, before the clear, it will run callback before execution the clear.
If IT arrive after the clear, it will run callback regularly. So from my point of view it is not harmful to make the clear.
And Clear is necessary for example in the following use case:
The timer is already running with interrupts disabled. After a while, Flags Update or Compare will be set. Then if we enable interrupt we must clear the flag to discard past interrupts and take into account only new ones.
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 am not entirely sure about the timing details on STM32, but I can imagine that the interrupt arrives at the same time as the clear, and thus is cleared before it can run.
Another case is when code is called when interrupts are temporarily disabled (e.g. using the global interrupt mask, or when running from an ISR with higher priority). In that case, the flag might be set before the clear, but the interrupt cannot run yet, so the clear loses one interrupt.
And Clear is necessary for example in the following use case:
The timer is already running with interrupts disabled. After a while, Flags Update or Compare will be set. Then if we enable interrupt we must clear the flag to discard past interrupts and take into account only new ones.
Yeah, I agree on that. Hence my suggestion to only clear if the interrupt is not enabled yet, which I think should cover the case you describe? If the interrupt is already enabled, then the clear is not needed, assuming that any interrupts that are still pending should be handled by the new callback rather than be lost.
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.
Right, I agree with your proposal. I implement it in #852
Hi @matthijskooijman,
Also, I think about taking only part of your proposal: in attachInterrupt(), enable IT only if channel is running.
That is right that if you call attachInterrupt() while channel is no running, it will enable the IT, and it can fire the callback. Up to application to configure channel 1rst. |
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 will push update for race condition in separate PR since this one is closed
if ((channel == 0) || (channel > (TIMER_CHANNELS + 1))) { | ||
Error_Handler(); // only channel 1..4 have an interrupt | ||
} | ||
|
||
if (callback != NULL) { | ||
// Clear flag before enabling IT | ||
__HAL_TIM_CLEAR_FLAG(&(_timerObj.handle), interrupt); |
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 understand what you said, but Idisagree:
In the case you change the callback while timer is running, you will not loose interrupt, because if IT is pending, before the clear, it will run callback before execution the clear.
If IT arrive after the clear, it will run callback regularly. So from my point of view it is not harmful to make the clear.
And Clear is necessary for example in the following use case:
The timer is already running with interrupts disabled. After a while, Flags Update or Compare will be set. Then if we enable interrupt we must clear the flag to discard past interrupts and take into account only new ones.
__HAL_TIM_CLEAR_FLAG(&(_timerObj.handle), TIM_FLAG_UPDATE); | ||
// Enable update interrupt only if callback is valid | ||
__HAL_TIM_ENABLE_IT(&(_timerObj.handle), TIM_IT_UPDATE); | ||
} | ||
callbacks[0] = callback; |
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.
Right, I will take it into account.
__HAL_TIM_CLEAR_FLAG(&(_timerObj.handle), interrupt); | ||
// Enable interrupt corresponding to channel, only if callback is valid | ||
__HAL_TIM_ENABLE_IT(&(_timerObj.handle), interrupt); | ||
} | ||
callbacks[channel] = callback; |
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.
Right, I will take it into account.
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 disagree with your proposal to enable interrupt only in resume()/resumeChannel() and only when timer or channel is running:
My proposal was probably not well-enough thought-through, partly meant as food for more thought than a specific proposal. I had another look just now, but it's been a while since I looked at the code, and I have no time right now to dive in again deep enough to come up with some proper examples and proposals, so maybe later.
I did respond to an inline comment again.
Also, the changes you made in #835 and #849 look good to me!
rework stm32duino#806 after discussion with @matthijskooijman
rework #806 after discussion with @matthijskooijman
Summary
Rework HardwareTimer internal working. And add new API to pause/resume individual channel.
Main rework:
HAL_TIM_Base_Init()
is now called only once at object creationHAL_TIM_xxx_ConfigChannel
is now done insetMode()
HAL_TIM_xxx_Start
is done inresumeChannel()
then remove useless attribute