-
Notifications
You must be signed in to change notification settings - Fork 13.3k
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
[BUG] tone doesn't work after analogWrite #4598
Comments
@igrr do you know anything about this? |
Just to clarify, what is the sequence of calls to Arduino functions which triggers this problem? |
This is very simple. Just call:
After this a normal timer1 interrupt, which is initialized by:
doesn't work anymore So when I use analogWrite in core version 2.4.1, which uses this NMI interupt now, tone doesn't work anymore. It needs a reset, before tone works again. |
ETS_blah aren't Arduino APIs, but On a separate note, it probably makes sense for |
Why is now a timer1 NMI interrupt used in PWM? A NMI interrupt has a reaction time of nearly 10 µs. A normal timer1 interrupt needs only 4 µs. But there needn't be changes done now, because I now have implemented a module timer_shared.cpp:
And this is the header file timer_shared.h:
After using these timer interface functions in core_esp8266_wiring_pwm.c:
And in Tone.cpp:
Tone and PWM may work both at the same time!!! |
Besides of this, tone should be more optimized. Tone uses digitalWrite. digitalWrite calls pwm_stop_pin and so needs 69 CPU ticks. Without pwm_stop_pin, it's 37 CPU ticks as ICACHE_RAM_ATTR and only 30 CPU ticks otherwise. If we make two functions, one for pins except D0 and one for D0, this only needs 10 CPU ticks. I didn't change this, but only inserted my timer_shared interface. |
|
Yes, this is a reason, but timer1 cannot be used thereafter anymore, because there is a bug in the SDK for ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL). And for small PWM values below 10 the NMI timer1 interrupt isn't accurate enough. But if somebody disables the interrupts, an NMI interrupt would be better. For an NMI interrupt for my shared_timer module additionally shared_timer_nmi_isr_init() should be called in the tone and pwm module. Or the user may decide self, whether he wants NMI or not. If he calls shared_timer_nmi_isr_init(), then timer_shared runs as NMI timer1 interrupt. |
Thank you, this is the bug you just reported. I'll have a look :)
That is true. I think for low PWM values we can optimize the PWM implementation to busy-loop for a few microseconds, instead of setting up a timer. |
Before implementation there should be thoughts. Why would a user want to disable Interrupts? Could be because he had implemented an own ISR yand wants to change values for the ISR. If the user has implemented an own ISR, he wants, that it performs well and doesn't want some busy loop within another ISR. Should the user use noInterrupts() and interupts(). Measure the execution time. This isn't a good idea. But uint32_t savedPS = xt_rsil(15) and xt_wsr_ps(savedPS) each need only 9 ticks. But stopping interrupts isn't necessary. Parameters may be prepared for the interrupt, and when the interrupt is triggered, it may take them over. When the user knows this, then there isn't a reason for NMI timer interrupts in PWM. And a busy loop will also have negative consequences for other functionalities like pulseIn() and who knows, what else. The problem is, when many people or departments develop a software without enough consultation, and many people have such ideas as different busy loops, the system will not perform well. Maybe this is also a problem of the SDK. Or how can somebody explain 358 ticks for disabling a normal timer1 isr ETS_FRC1_INTR_DISABLE(); // 358 ticks? |
It's not necessarily the user who has disabled the interrupts, it could be e.g. he's calling an api with timing critical code under the hood, like OneWire, where the interrupts must be disabled for the generation of the protocol pulses, or it won't work. |
Besides, even if user is not disabling interrupts, some interrupt (like WiFi interrupt) might be consuming (some) CPU time. Since both wifi and timer interrupts are level 1 interrupts, one can not preempt the other, so PWM output will be disturbed when there is a lot of wifi traffic. Attaching the timer to a different interrupt (which is above level 1) is done to allow preemption and get less jitter when WiFi is handling traffic. |
@igrr I didn't measure a difference between NMI and non NMI timer ISR. But there is a bug in PWM. What would you think about values near maximum? For a value of 1020 I measured these times in microseconds via pulseIn: 1000, 1998, 1001, 4007, 3006, 1001, 1001, 1001, 3000, 1998 Seems to be a lot of jitter, but of course, it shouldn't matter much, whether we have a full cycle without switchung some microseconds to LOW. Another question is, why is it so important, that we don't have some jitter and what about the accuracy of pulseIn? Could also be, that pulseIn jitters. So we don't know, whether jitter measured by pulseIn is jitter of the signal or of pulseIn. Of course the values above are not jitter of pulseIn, but a missing LOW for values near maximum. No, this also could be jitter of pulseIn: if the flank is too short, pulseIn maybe doesn't catch it. |
@AlfonsMittelmeyer you probably should be aware that there is reported flickering in the current pwm implementation, reference: #836 #2592 #2621 #2675 . There are reports of a better implementation for pwm ( see discussion in #836 ), but that particular implementation can't be integrated here due to licensing incompatibility. |
@devyte I noticed such flickering, when I tested with blinking for cycles below 300 ticks when using non NMI timer isr and for cycles below 800 ticks when using NMI timer isr. We need to find out about the reason. @igrr Because I didn't notice a difference between non NMI timer isr and NMI timer isr for PWM in the case of Wifi not running, there seems to be no reason, why we shouldn't use NMI. And my opinion now is also, that NMI should be used for PWM. Of course, measuring via pulseIn produces jitter, if there is an ISR running. We cannot have PWM and PulseIn performing well at the same time, when using only one ESP8266. For exact measurements timer0 could be used in NMI mode and timer1 in non NMI mode for PWM, in the case, if some jitter would be acceptable. What the priorities are, depend what the user wants to do. I would think, that the best way would be, if the user could configure the priorities for his application. I will set NMI mode as default for my timer_shared module und I will do measurements about ticks for implementing exact time spans. There are some questions to be answered like how many ticks does it take from timer becoming 0 until the timer isr called and some further similar questions. |
@igrr currently the time spans for small PWM values are too long, because it wasn't considered, that it takes time until the ISR is called. I made some tests by using this sketch:
This is the result for non NMI isr for 80 MHz CPU clock:
1 tick for calling asm_ccount(), 10 ticks for calling T1L. Normally the code doesn't have asm_ccount(), so swe have to subtract 2 ticks for two times asm_ccount and one tick, for value 1 used in T1L = 1. This means, that we have a delay of 118 ticks, when using non NMI isr. The results for NMI isr were:
When T1L is used the first time for a NMI isr, it has an additional delay of 314 ticks. But this shouldn't matter. The delay for NMI isr is 130 ticks instead of 118 ticks for non NMI or 119 ticks, if we disable other interrupts via uint32_t savedPS = xt_rsil(15; This behaviour I will consider for the tick calculation in my timer shared module. For smaller values an idle loop within the isr would make sense. Most probably the delay for 160 MHz CPU clock in ticks will be different, because not every part of the ESP8266 will become more fast. Storing a value into a variable in the RAM costs 8 ticks, when using 80 MHz, how many ticks will this be for 160 MHz? |
NMI and not NMI isr shall have the same timing. This we may do by:
|
@AlfonsMittelmeyer I see you are sharing valuable progress here, but i ask you to keep discussion in issues on topic, for everyone's sanity. Specifically, this one tracks the bug in SDK that Timer can not be used with non-NMI interrupt once NMI has been used. For other issues (such as which interrupt to use, NMI vs non-NMI, and so on), please open separate tickets. FWIW, I have reported the issue about |
@AlfonsMittelmeyer we've released 2.4.2 which doesn't use NMI and which lets tone/analogWrite run in any order or at the same time. So this specific bug is fixed as far as I understand the initial problem. I know you had a good start on optimizing things, so if you have a PR to submit that helps bring down jitter using some of the techniques you were discussing, we'd love to see it! |
In core version 2.3.0 analogWrite was implemented by a normal timer1 interrupt. In core version 2.4.0 a timer1 NMI interrupt is used (core_esp8266_timer.c). Therefore in tools/sdk/include/ets_sys.h the function ETS_FRC_TIMER1_NMI_INTR_ATTACH(func) exists. The timer1 NMI interrupt may be detached by using a NULL pointer as parameter for this function. But a normal timer1 interrupt via ETS_FRC_TIMER1_INTR_ATTACH(func, arg) and ETS_FRC1_INTR_ENABLE() doesn't work anymore after. Looks like a bug of the espressif sdk.
The text was updated successfully, but these errors were encountered: