2626// / i.e. If not, assume a 100% duty cycle. Ignore attempts to change the
2727// / duty cycle etc.
2828IRsend::IRsend (uint16_t IRsendPin, bool inverted, bool use_modulation)
29- : IRpin(IRsendPin), periodOffset( kPeriodOffset ) {
29+ : IRpin(IRsendPin) {
3030 if (inverted) {
3131 outputOn = LOW;
3232 outputOff = HIGH;
@@ -54,7 +54,7 @@ IRsend::IRsend(uint16_t IRsendPin, bool inverted, bool use_modulation)
5454// / @param[in] w1tc_mask mask for clearing GPIOs for a logical 1 output.
5555// / Required if some outputs are inverted.
5656IRsend::IRsend (bool use_modulation, int64_t w1ts_mask, uint64_t w1tc_mask) :
57- periodOffset( kPeriodOffset ), _irPinMaskEnabled(true ) {
57+ _irPinMaskEnabled(true ) {
5858 IRpin = static_cast <int32_t >(w1ts_mask);
5959 _w1ts_mask_upper = static_cast <int32_t >(w1ts_mask >> 32 );
6060 _w1tc_mask_lower = static_cast <int32_t >(w1tc_mask);
@@ -152,18 +152,14 @@ void IRsend::ledOn() {
152152
153153// / Calculate the period for a given frequency.
154154// / @param[in] hz Frequency in Hz.
155- // / @param[in] use_offset Should we use the calculated offset or not?
156155// / @return nr. of uSeconds.
157156// / @note (T = 1/f)
158- uint32_t IRsend::calcUSecPeriod (uint32_t hz, bool use_offset ) {
157+ uint32_t IRsend::calcUSecPeriod (uint32_t hz) {
159158 if (hz == 0 ) hz = 1 ; // Avoid Zero hz. Divide by Zero is nasty.
160159 uint32_t period =
161160 (1000000UL + hz / 2 ) / hz; // The equiv of round(1000000/hz).
162- // Apply the offset and ensure we don't result in a <= 0 value.
163- if (use_offset)
164- return std::max (static_cast <uint32_t >(1 ), period + periodOffset);
165- else
166- return std::max (static_cast <uint32_t >(1 ), period);
161+ // Ensure we don't result in a <= 0 value.
162+ return std::max (static_cast <uint32_t >(1 ), period);
167163}
168164
169165// / Set the output frequency modulation and duty cycle.
@@ -188,11 +184,34 @@ void IRsend::enableIROut(uint32_t freq, uint8_t duty) {
188184#ifdef UNIT_TEST
189185 _freq_unittest = freq;
190186#endif // UNIT_TEST
187+
188+ #ifndef UNIT_TEST
189+ _fractionalBits = 14 ;
190+
191+ // Maximum signed value that fits.
192+ uint32_t maxValue = 0x7FFF >> _fractionalBits;
193+ uint32_t period = calcUSecPeriod (freq);
194+
195+ // Decrement the number of fractional bits until the period fits.
196+ while (maxValue < period) {
197+ --_fractionalBits;
198+ maxValue = 0x7FFF >> _fractionalBits;
199+ }
200+
201+ uint32_t fixedPointPeriod = ((1000000ULL << _fractionalBits) + freq / 2 )
202+ / freq;
203+
204+ // Nr. of uSeconds the LED will be on per pulse.
205+ onTimePeriod = (fixedPointPeriod * _dutycycle) / kDutyMax ;
206+ // Nr. of uSeconds the LED will be off per pulse.
207+ offTimePeriod = fixedPointPeriod - onTimePeriod;
208+ #else
191209 uint32_t period = calcUSecPeriod (freq);
192210 // Nr. of uSeconds the LED will be on per pulse.
193211 onTimePeriod = (period * _dutycycle) / kDutyMax ;
194212 // Nr. of uSeconds the LED will be off per pulse.
195213 offTimePeriod = period - onTimePeriod;
214+ #endif
196215}
197216
198217#if ALLOW_DELAY_CALLS
@@ -253,6 +272,67 @@ uint16_t IRsend::mark(uint16_t usec) {
253272 // Not simple, so do it assuming frequency modulation.
254273 uint16_t counter = 0 ;
255274 IRtimer usecTimer = IRtimer ();
275+ #ifndef UNIT_TEST
276+ #if SEND_BANG_OLUFSEN && ESP8266 && F_CPU < 160000000L
277+ // Free running loop to attempt to get close to the 455 kHz required by
278+ // Bang & Olufsen.
279+ // Define BANG_OLUFSEN_CHECK_MODULATION temporarily to test frequency and
280+ // time.
281+ // Runs at ~300 kHz on an 80 MHz ESP8266.
282+ // This is far from ideal but works if the transmitter is close enough.
283+ uint32_t periodUInt = (onTimePeriod + offTimePeriod) >> _fractionalBits;
284+ periodUInt = std::max (static_cast <uint32_t >(1 ), periodUInt);
285+ if (periodUInt <= 5 ) {
286+ // Assume we can at least run for this number of periods.
287+ uint32_t nextCheck = usec / periodUInt / 2 ;
288+ for (;;) { // nextStop is not updated in this loop.
289+ ledOn ();
290+ ledOff ();
291+ counter++;
292+ if (counter >= nextCheck) {
293+ uint32_t now = usecTimer.elapsed ();
294+ int32_t timeLeft = usec - now;
295+ if (timeLeft <= 1 ) {
296+ return counter;
297+ }
298+ uint32_t periodsToEnd = counter * timeLeft / now;
299+ // Check again when we are half way closer to the end.
300+ nextCheck = (periodsToEnd >> 2 ) + counter;
301+ }
302+ }
303+ }
304+ #endif
305+
306+ // Use absolute time for zero drift (but slightly uneven period).
307+ // Using IRtimer.elapsed() instead of _delayMicroseconds is better for short
308+ // period times.
309+ // Maxed out at ~190 kHz on an 80 MHz ESP8266.
310+ // Maxed out at ~460 kHz on ESP32.
311+ // Must be 32 bits to not overflow when usec is near max.
312+ uint32_t nextStop = 0 ;
313+ // Loop until we've met/exceeded our required time.
314+ while ((nextStop >> _fractionalBits) < usec) {
315+ ledOn ();
316+ nextStop += onTimePeriod;
317+ uint32_t nextStopUInt = std::min (nextStop >>
318+ _fractionalBits, static_cast <uint32_t >(usec));
319+ while (usecTimer.elapsed () < nextStopUInt) {}
320+ ledOff ();
321+ counter++;
322+ nextStop += offTimePeriod;
323+ nextStopUInt = std::min (nextStop >>
324+ _fractionalBits, static_cast <uint32_t >(usec));
325+ uint32_t now = usecTimer.elapsed ();
326+ int32_t delay = nextStopUInt - now;
327+ if (delay > 0 ) {
328+ while (usecTimer.elapsed () < nextStopUInt) {}
329+ } else {
330+ // This means we ran past nextStop and need to reset to actual time to
331+ // avoid playing catch-up with a far too short period.
332+ nextStop = (now << _fractionalBits) + (offTimePeriod >> 1 );
333+ }
334+ }
335+ #else
256336 // Cache the time taken so far. This saves us calling time, and we can be
257337 // assured that we can't have odd math problems. i.e. unsigned under/overflow.
258338 uint32_t elapsed = usecTimer.elapsed ();
@@ -273,6 +353,7 @@ uint16_t IRsend::mark(uint16_t usec) {
273353 static_cast <uint32_t >(offTimePeriod)));
274354 elapsed = usecTimer.elapsed (); // Update & recache the actual elapsed time.
275355 }
356+ #endif
276357 return counter;
277358}
278359
@@ -296,7 +377,7 @@ void IRsend::space(uint32_t time) {
296377int8_t IRsend::calibrate (uint16_t hz) {
297378 if (hz < 1000 ) // Were we given kHz? Supports the old call usage.
298379 hz *= 1000 ;
299- periodOffset = 0 ; // Turn off any existing offset while we calibrate.
380+ int8_t periodOffset = 0 ;
300381 enableIROut (hz);
301382 IRtimer usecTimer = IRtimer (); // Start a timer *just* before we do the call.
302383 uint16_t pulses = mark (UINT16_MAX); // Generate a PWM of 65,535 us. (Max.)
0 commit comments