// Calculate & set any offsets to account for execution times. // // Args: // hz: The frequency to calibrate at >= 1000Hz. Default is 38000Hz. // // Status: ALPHA / Untested. // // NOTE: // This will generate an 65535us mark() IR LED signal. // This only needs to be called once, if at all. void IRsend::calibrate(uint16_t hz) { if (hz < 1000) // Were we given kHz? Supports the old call usage. hz *= 1000; periodOffset = 0; // Turn off any existing offset while we calibrate. enableIROut(hz); IRtimer usecTimer = IRtimer(); // Start a timer *just* before we do the call. uint16_t pulses = mark(UINT16_MAX); // Generate a PWM of 65,535 us. (Max.) uint32_t timeTaken = usecTimer.elapsed(); // Record the time it took. // While it shouldn't be neccesary, assume at least 1 pulse, to avoid a // divide by 0 situation. pulses = std::max(pulses, (uint16_t) 1U); uint32_t calcPeriod = calcUSecPeriod(hz); // e.g. @38kHz it should be 26us. // Assuming 38kHz for the example calculations: // In a 65535us pulse, we should have 2520.5769 pulses @ 26us periods. // e.g. 65535.0us / 26us = 2520.5769 // This should have caused approx 2520 loops through the main loop in mark(). // The average over that many interations should give us a reasonable // approximation at what offset we need to use to account for instruction // execution times. // // Calculate the actual period from the actual time & the actual pulses // generated. double_t actualPeriod = (double_t) timeTaken / (double_t) pulses; // Store the difference between the actual time per period vs. calculated. periodOffset = (int8_t) ((double_t) calcPeriod - actualPeriod); }
// Modulate the IR LED for the given period (usec) and at the duty cycle set. // // Args: // usec: The period of time to modulate the IR LED for, in microseconds. // Returns: // Nr. of pulses actually sent. // // Note: // The ESP8266 has no good way to do hardware PWM, so we have to do it all // in software. There is a horrible kludge/brilliant hack to use the second // serial TX line to do fairly accurate hardware PWM, but it is only // available on a single specific GPIO and only available on some modules. // e.g. It's not available on the ESP-01 module. // Hence, for greater compatibility & choice, we don't use that method. // Ref: // https://www.analysir.com/blog/2017/01/29/updated-esp8266-nodemcu-backdoor-upwm-hack-for-ir-signals/ uint16_t IRsend::mark(uint16_t usec) { uint16_t counter = 0; IRtimer usecTimer = IRtimer(); // Cache the time taken so far. This saves us calling time, and we can be // assured that we can't have odd math problems. i.e. unsigned under/overflow. uint32_t elapsed = usecTimer.elapsed(); while (elapsed < usec) { // Loop until we've met/exceeded our required time. #ifndef UNIT_TEST digitalWrite(IRpin, outputOn); // Turn the LED on. // Calculate how long we should pulse on for. // e.g. Are we to close to the end of our requested mark time (usec)? delayMicroseconds(std::min((uint32_t) onTimePeriod, usec - elapsed)); digitalWrite(IRpin, outputOff); // Turn the LED off. #endif counter++; if (elapsed + onTimePeriod >= usec) return counter; // LED is now off & we've passed our allotted time. // Wait for the lesser of the rest of the duty cycle, or the time remaining. #ifndef UNIT_TEST delayMicroseconds(std::min(usec - elapsed - onTimePeriod, (uint32_t) offTimePeriod)); #endif elapsed = usecTimer.elapsed(); // Update & recache the actual elapsed time. } return counter; }
// Modulate the IR LED for the given period (usec) and at the duty cycle set. // // Args: // usec: The period of time to modulate the IR LED for, in microseconds. // Returns: // Nr. of pulses actually sent. // // Note: // The ESP8266 has no good way to do hardware PWM, so we have to do it all // in software. There is a horrible kludge/brilliant hack to use the second // serial TX line to do fairly accurate hardware PWM, but it is only // available on a single specific GPIO and only available on some modules. // e.g. It's not available on the ESP-01 module. // Hence, for greater compatibility & choice, we don't use that method. // Ref: // https://www.analysir.com/blog/2017/01/29/updated-esp8266-nodemcu-backdoor-upwm-hack-for-ir-signals/ uint16_t IRsend::mark(uint16_t usec) { // Handle the simple case of no required frequency modulation. if (!modulation || _dutycycle >= 100) { ledOn(); _delayMicroseconds(usec); ledOff(); return 1; } // Not simple, so do it assuming frequency modulation. uint16_t counter = 0; IRtimer usecTimer = IRtimer(); // Cache the time taken so far. This saves us calling time, and we can be // assured that we can't have odd math problems. i.e. unsigned under/overflow. uint32_t elapsed = usecTimer.elapsed(); while (elapsed < usec) { // Loop until we've met/exceeded our required time. ledOn(); // Calculate how long we should pulse on for. // e.g. Are we to close to the end of our requested mark time (usec)? _delayMicroseconds(std::min((uint32_t) onTimePeriod, usec - elapsed)); ledOff(); counter++; if (elapsed + onTimePeriod >= usec) return counter; // LED is now off & we've passed our allotted time. // Wait for the lesser of the rest of the duty cycle, or the time remaining. _delayMicroseconds(std::min(usec - elapsed - onTimePeriod, (uint32_t) offTimePeriod)); elapsed = usecTimer.elapsed(); // Update & recache the actual elapsed time. } return counter; }
// Send a Philips RC-MM packet. // // Args: // data: The data we want to send. MSB first. // nbits: The number of bits of data to send. (Typically 12, 24, or 32[Nokia]) // repeat: The nr. of times the message should be sent. // // Status: BETA / Should be working. // // Ref: // http://www.sbprojects.com/knowledge/ir/rcmm.php void IRsend::sendRCMM(uint64_t data, uint16_t nbits, uint16_t repeat) { // Set 36kHz IR carrier frequency & a 1/3 (33%) duty cycle. enableIROut(36, 33); IRtimer usecs = IRtimer(); for (uint16_t r = 0; r <= repeat; r++) { usecs.reset(); // Header mark(RCMM_HDR_MARK); space(RCMM_HDR_SPACE); // Data uint64_t mask = 0b11ULL << (nbits - 2); // RC-MM sends data 2 bits at a time. for (int32_t i = nbits; i > 0; i -= 2) { mark(RCMM_BIT_MARK); // Grab the next Most Significant Bits to send. switch ((data & mask) >> (i - 2)) { case 0b00: space(RCMM_BIT_SPACE_0); break; case 0b01: space(RCMM_BIT_SPACE_1); break; case 0b10: space(RCMM_BIT_SPACE_2); break; case 0b11: space(RCMM_BIT_SPACE_3); break; } mask >>= 2; } // Footer mark(RCMM_BIT_MARK); // Protocol requires us to wait at least RCMM_RPT_LENGTH usecs from the // start or RCMM_MIN_GAP usecs. space(std::max(RCMM_RPT_LENGTH - usecs.elapsed(), RCMM_MIN_GAP)); } }
// Send a Mitsubishi message // // Args: // data: Contents of the message to be sent. // nbits: Nr. of bits of data to be sent. Typically MITSUBISHI_BITS. // repeat: Nr. of additional times the message is to be sent. // // Status: ALPHA / untested. // // Notes: // This protocol appears to have no header. // Ref: // https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Mitsubishi.cpp // GlobalCache's Control Tower's Mitsubishi TV data. void IRsend::sendMitsubishi(uint64_t data, uint16_t nbits, uint16_t repeat) { enableIROut(33); // Set IR carrier frequency IRtimer usecTimer = IRtimer(); for (uint16_t i = 0; i <= repeat; i++) { usecTimer.reset(); // No header // Data sendData(MITSUBISHI_BIT_MARK, MITSUBISHI_ONE_SPACE, MITSUBISHI_BIT_MARK, MITSUBISHI_ZERO_SPACE, data, nbits, true); // Footer mark(MITSUBISHI_BIT_MARK); space(std::max(MITSUBISHI_MIN_COMMAND_LENGTH - usecTimer.elapsed(), MITSUBISHI_MIN_GAP)); } }
// Send a Whynter message. // // Args: // data: message to be sent. // nbits: Nr. of bits of the message to be sent. // repeat: Nr. of additional times the message is to be sent. // // Status: STABLE // // Ref: // https://github.com/z3t0/Arduino-IRremote/blob/master/ir_Whynter.cpp void IRsend::sendWhynter(uint64_t data, uint16_t nbits, uint16_t repeat) { // Set IR carrier frequency enableIROut(38); IRtimer usecTimer = IRtimer(); for (uint16_t i = 0; i <= repeat; i++) { usecTimer.reset(); // (Pre-)Header mark(WHYNTER_BIT_MARK); space(WHYNTER_ZERO_SPACE); sendGeneric(WHYNTER_HDR_MARK, WHYNTER_HDR_SPACE, WHYNTER_BIT_MARK, WHYNTER_ONE_SPACE, WHYNTER_BIT_MARK, WHYNTER_ZERO_SPACE, WHYNTER_BIT_MARK, WHYNTER_MIN_GAP, data, nbits, 38, true, 0, // Repeats are already handled. 50); space(std::max(WHYNTER_MIN_COMMAND_LENGTH - WHYNTER_MIN_GAP - usecTimer.elapsed(), 0U)); } }