Skip to content
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

Carrier frequency inaccurate #62

Closed
kvoit opened this issue Dec 26, 2016 · 22 comments
Closed

Carrier frequency inaccurate #62

kvoit opened this issue Dec 26, 2016 · 22 comments
Assignees
Milestone

Comments

@kvoit
Copy link

kvoit commented Dec 26, 2016

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.

@crankyoldgit
Copy link
Owner

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.

@kvoit
Copy link
Author

kvoit commented Feb 24, 2017

I am not sure if you got me right. I did not try to match 32.5 kHz.

  • 32.5 kHz is what I got from the ESP when 35kHz was configured.
  • With the proposed change, I got pretty accurate 35kHz
  • The original remote uses 36.5kHz

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:

  • The DeepSleep wakeup timer seems to vastly inaccurate (in the percentage range and bigger)
  • An LED with programmatically untouched PWM on GPIO15 fades and flickers when I read a DHT22 on GPIO0.
    The ESP is not an ATMEGA, eventually. It's single core, it has to do stuff in the background. There seem to be catches.

@crankyoldgit
Copy link
Owner

crankyoldgit commented Feb 24, 2017

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. :-(

@crankyoldgit crankyoldgit self-assigned this Feb 24, 2017
@kvoit
Copy link
Author

kvoit commented Feb 24, 2017

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.

@eMDi101
Copy link

eMDi101 commented Feb 26, 2017

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/

@crankyoldgit
Copy link
Owner

crankyoldgit commented Feb 27, 2017

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?

@kvoit
Copy link
Author

kvoit commented Feb 27, 2017

Since it is a hack for UART1, I am pretty sure it only works on GPIO2.

@eMDi101
Copy link

eMDi101 commented Feb 27, 2017

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.

@crankyoldgit
Copy link
Owner

crankyoldgit commented Feb 27, 2017

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.

@crankyoldgit
Copy link
Owner

Apologies for the accidental close. Fat fingered/trackpad'ed.

@crankyoldgit
Copy link
Owner

crankyoldgit commented Apr 18, 2017

@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.

@crankyoldgit crankyoldgit added this to the v2.0 milestone Apr 18, 2017
@kvoit
Copy link
Author

kvoit commented Apr 18, 2017

Thanks for looking into this. I'll try to test this next weekend.

@WStille
Copy link

WStille commented May 2, 2017

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.

#include <IRremoteESP8266.h>
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;

@crankyoldgit
Copy link
Owner

@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 added a commit that referenced this issue May 3, 2017
Apply an external offset for the period calculation so we
allow for code/execution time in producing the software based PWM signal
in mark().
For Issue #62
@WStille
Copy link

WStille commented May 3, 2017

@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;

@crankyoldgit
Copy link
Owner

@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.

@WStille
Copy link

WStille commented May 3, 2017

@crankyoldgit Self calibration would be really great!

crankyoldgit added a commit that referenced this issue May 3, 2017
- First check in of a routine to calculate the execution delays in mark()
  and create an offset based on that for future mark() calls.

Note: Requires a user to call IRsend.calibrate() before sending a signal.
      If not, defaults to '-3' as determined experimentally in Issue #62

WARNING: The calibrate() routine will generate a PWM pulse when called, thus
         it is left as an optional step for users who are interested.
@crankyoldgit
Copy link
Owner

@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 added a commit that referenced this issue May 3, 2017
- First check in of a routine to calculate the execution delays in mark()
  and create an offset based on that for future mark() calls.

Note: Requires a user to call IRsend.calibrate() before sending a signal.
      If not, defaults to '-3' as determined experimentally in Issue #62

WARNING: The calibrate() routine will generate a PWM pulse when called, thus
         it is left as an optional step for users who are interested.
crankyoldgit added a commit that referenced this issue May 3, 2017
Apply an external offset for the period calculation so we
allow for code/execution time in producing the software based PWM signal
in mark().
For Issue #62
crankyoldgit added a commit that referenced this issue May 3, 2017
- First check in of a routine to calculate the execution delays in mark()
  and create an offset based on that for future mark() calls.

Note: Requires a user to call IRsend.calibrate() before sending a signal.
      If not, defaults to '-3' as determined experimentally in Issue #62

WARNING: The calibrate() routine will generate a PWM pulse when called, thus
         it is left as an optional step for users who are interested.
@WStille
Copy link

WStille commented May 3, 2017

@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

@crankyoldgit
Copy link
Owner

\ o /
I call that success.
Many many thanks for the confirmation, the data, and heck, for the idea in the first place.

crankyoldgit added a commit that referenced this issue May 3, 2017
- Apply an external offset for the period calculation so we
allow for code/execution time in producing the software based PWM signal
in mark().
- Add a simple calibrate() method to IRsend().
- First check in of a routine to calculate the execution delays in mark()
  and create an offset based on that for future mark() calls.

Note: Requires a user to call IRsend.calibrate() before sending a signal.
      If not, defaults to '-3' as determined experimentally in Issue #62

WARNING: The calibrate() routine will generate a PWM pulse when called, thus
         it is left as an optional step for users who are interested.
crankyoldgit added a commit that referenced this issue May 4, 2017
- Apply an external offset for the period calculation so we
allow for code/execution time in producing the software based PWM signal
in mark().
- Add a simple calibrate() method to IRsend().
- First check in of a routine to calculate the execution delays in mark()
  and create an offset based on that for future mark() calls.

Note: Requires a user to call IRsend.calibrate() before sending a signal.
      If not, defaults to '-3' as determined experimentally in Issue #62

WARNING: The calibrate() routine will generate a PWM pulse when called, thus
         it is left as an optional step for users who are interested.
@crankyoldgit
Copy link
Owner

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.

@crankyoldgit
Copy link
Owner

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.

Repository owner locked as resolved and limited conversation to collaborators Jun 1, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants