I am just setting up an MQTT-to-IR bridge using a Wemos D1 Mini, but was unable to control our old Panasonic CRT TV. Hooking up the oscilloscope, I found out that the actual carrier frequency was about 32.5kHz instead of the configured 35kHz. The actual remote even uses 36.5kHz. This discrepancy was apparently enough so that the TV did not react at all.
As a quick and dirty fix, I changed the timing in IRsend::enableIROut(int khz) from 500/khz to 465/khz. This gave me a pretty accurate carrier frequency for 35 and the TV works, but I expect that the issue is rather related to additional delays, so an offset would be more appropriate to get accurate results on other frequencies.
Hey. Thanks for that. I've noticed there can be delays that can cause sub-optimal frequency matching.
Rather than make the 500 -> 465 change, which I admit is a great match for 32.5kHz, have you tried using enableIROut(38) for 38kHz? or 33 etc?
From some quick searches, most other emulation of Panasonic codes seem to favour a higher freq than the 35kHz in our library. I don't have any devices to check with.
I am not sure if you got me right. I did not try to match 32.5 kHz.
I mean, I guess just switching to 38kHz would have given me a smaller but working frequency.
What I actually did after writing this bug report is putting in different delays and try to do a linear regression. I did not find any reasonable offset >1µs, though. The fix I suggested above gave decent values over the whole order of magnitude in the frequency. I find that strange too, but it was what I got on the oscilloscope. If I had gotten something reasonable there, I would have written a PR.
I start to suspect that there is more going on than a delay. It is not the only strange timer related effect I have found on the ESP recently:
You are right. I was too quick and failed on the math. I calculated 465/500*35000 = 32550Hz. Instead of 500/465*35000 = 37634Hz. I'm not sure allowing for a 7% slew for everything (changing 500->465) is wise as it may stuff up other protocols and assumptions others have made. Using 38 for Panasonic limits the changes.
Yes, the timing/delays on the ESP8266 are not stellar. :-(
Well, changing 500 to 465 leads to the ESP outputting 35kHz physically, that is: measured with an oscilloscope.
I tested that this also improves frequency accuracy on a larger range of frequencies, not only 35, though I would appreciate if someone would second that.
If anybody relies on a function claiming to output 35kHz outputting something else than 35kHz, then I guess that is their fault, isn't it? People will CERTAINLY rely on enableIROut(35) outputting 35kHz, which it does not, at the moment, at least not on my ESP.
can you add this nice uart hack? In my tests it worked very well.
https://www.analysir.com/blog/2017/01/29/updated-esp8266-nodemcu-backdoor-upwm-hack-for-ir-signals/
That's an interesting solution. I'm not sure I follow it completely, but does it work on any arbitrary PIN/GPIO that are capable of PWM, or just the combo D4/GPIO2?
Since it is a hack for UART1, I am pretty sure it only works on GPIO2.
it works only with the second serial line GPIO2/D4 and you have to add an 4k7 ohm pullup resistor between GPIO2 and 3,3V to avoid ESP boot issues. it is not pwm modulated, it is modulated by the serial baudrate and a serial character like 0xF0 (50% duty cycle). The baudrate rate must be 10 times the carrier frequency. you can also change the duty cycle if you send an other character like 0xFC 30% duty cycle or 0xFF 10% duty cycle.
Yeah, that's what I figured it might be doing. Wasn't sure. As the current library works on any digital GPIO, I don't think coding a specific solution just for this particular case is the best course of action at this point.
Apologies for the accidental close. Fat fingered/trackpad'ed.
@kvoit Your ground truth data pointed me to an oops in the upstream library and ours. I checked multiple sources and yes, you are most correct. That protocol uses 36.7kHz.
Any chance you can try the latest master _as is_ and see how it behaves for your panasonic device?
V2.0 will hopefully make things better still, but that's at least a few weeks off.
@AnalysIR FYI. Confirmation (via oscilloscope) of the Panasonic freq, and that it's for old devices, and, that some people still use that part of the library.
Thanks for looking into this. I'll try to test this next weekend.
I suggest to improve the timing by taking into account an extra delay due to the IRsend::mark() loop.
Using the simple test code below and measuring the generated carrier frequency at the IRsendPin
I found that the period was always too long by about 3 microseconds (tested with different arguments
of enableIROut() corresponding to 5, 10, 20 and 50 kHz). I got the same results for version 1.2.0
and branch v2.0-dev.
IRsend irsend(4);
void setup()
{
irsend.begin();
}
void loop() {
irsend.enableIROut(10);
irsend.mark(1000000);
}
The obtained periods T were:
irsend.enableIROut(5); // T = 203 µs
irsend.enableIROut(10); // T = 103 µs
irsend.enableIROut(20); // T = 53.4 µs
irsend.enableIROut(50); // T = 23.4 µs
The correction could be performed by changing the calculation of the period
in IRsend::calcUSecPeriod() (branch v2.0-dev) to
return (1000000UL + hz/2) / hz - 3;
@WStille Hey thanks for that. That looks great. I'll prep a change to do just that.
Can I ask how you measured it? e.g. With an oscilloscope? If so, I assume the 3 µs was from leading edge of a pulse to the next pulse. If you don't mind me asking for extra data, what was the time for the actual pulse? i.e. time for the leading edge to the trailing edge? Also for the non-pulse portion of the period.
I'm asking to see if we can improve the timings even further.
Oh, I assume you were triggering on the GPIO signal, not the output of the LED, because some LEDs may introduce an appreciable lag.
@crankyoldgit Thanks for your quick response.
I first used a very simple oscilloscope (DSO138), but its performance was not sufficient to
measure precisely the length of a full period (from trailing to trailing edge). However, it is
able to display the actual frequency (quite precisely) of a periodic signal. Within the
accuracy of that instrument, the duty cycle (at the GPIO pin) seems to be correct as
expected.
To improve the precision I also used a multimeter (UT61C), which has a quite precise
frequency counter mode for the range from 10 Hz to 10 MHz with an accuracy of
±(0.1 % + 4 digits).
Both instruments are not of a high end professional class, but the correct increment of
the periods for the different arguments to enableIROut() indicates that the results
are correct. That means that the increments of the periods of delayMicroseconds() and
hence the time base of the ESP8266 are also correct. The extra delay of 3 microseconds
must be due to the calls to digitalWrite() and usecTimer.elapsed(), the call overhead of
delayMicroseconds() and the loop overhead in mark() itself.
It would be great if someone having access to appropriate equipment could try to confirm
my findings. If the extra delay is found to be 3.4 microseconds (as indicated for the
results for 20 and 50 kHz), one could even improve the correction by rounding like ;-)
return (1000000UL + hz/2 - 34 * hz/10) / hz;
@WStille Thanks for the detailed reply. I don't have an oscilloscope, so any brand/make/model is better than no oscilloscope IMHO. ;-)
I like the idea of improving the rounding, but if we get within < 1µs with the simple -3 implementation, I'll be happy enough.
Honestly, I'd prefer a more robust solution that automatically catered for instruction time (i.e. later modification) and if someone chooses to over clock the ESP. e.g. default is 80MHz, but 160MHz is an available option.
Note to self: Add a calibration step to calculate the instruction time as part of the IRsend class setup.
@crankyoldgit Self calibration would be really great!
@WStille Want to try this branch out for me? And do some calcs/measurements?
I haven't really tested it well, so expect bugs etc. I may have screwed up. v.v.v.small chance it could burn out a IR LED, but I strongly doubt it.
All you should need to do is add calibrate() to your example setup() routine after you've cloned that branch .. I think.
e.g.
void setup()
{
irsend.begin();
irsend.calibrate();
}
If you don't add the calibrate() call, it _should_ default to the -3 usec offset you discussed.
Actually, I'd be interested in finding out the timings etc for both if you don't mind.
@crankyoldgit calibrate() seems to work fine!
I also found that it estimates periodOffset to be -3, as expected. Some results (same for
using calibrate() or the default of -3):
irsend.enableIROut(5); // T = 200 µs
irsend.enableIROut(10); // T = 100 µs
irsend.enableIROut(20); // T = 50.3 µs
irsend.enableIROut(50); // T = 20.2 µs
irsend.enableIROut(38); // T = 26.3 µs
\ o /
I call that success.
Many many thanks for the confirmation, the data, and heck, for the idea in the first place.
I think this issue is effectively closed now with that last commit to the v2.0 branch.
I'm anticipating v2.0 will be live by the end of the month.
First release candidate of v2.0 has been published. So I am going to mark this closed, as it includes a large number of timing improvements, some of which discussed here.
Most helpful comment
@crankyoldgit calibrate() seems to work fine!
I also found that it estimates periodOffset to be -3, as expected. Some results (same for
using calibrate() or the default of -3):
irsend.enableIROut(5); // T = 200 µs
irsend.enableIROut(10); // T = 100 µs
irsend.enableIROut(20); // T = 50.3 µs
irsend.enableIROut(50); // T = 20.2 µs
irsend.enableIROut(38); // T = 26.3 µs