1
0
mirror of https://github.com/taigrr/arduinolibs synced 2025-01-18 04:33:12 -08:00
arduinolibs/libraries/IR/IRreceiver.cpp
2012-06-09 12:50:05 +10:00

374 lines
12 KiB
C++

/*
* Copyright (C) 2012 Southern Storm Software, Pty Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "IRreceiver.h"
#if defined(ARDUINO) && ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#endif
/**
* \class IRreceiver IRreceiver.h <IRreceiver.h>
* \brief Manages the reception of RC-5 commands from an infrared remote control.
*
* IRreceiver recognizes commands in the <a href="http://en.wikipedia.org/wiki/RC-5">Philips RC-5 protocol</a>.
* This is a fairly common infrared protocol, supported by most universal
* remote controls. Program the universal remote to simulate a Philips
* TV, VCR, CD player, etc.
*
* This class uses interrupts to process incoming bits from a standard 3-pin
* infrared receiver:
*
* \image html irchip.jpg
*
* Typically, pin 1 of the receiver should be connected to the Arduino
* interrupt pin (e.g. D2), pin 2 should be connected to GND, and pin 3
* should be connected to 5V. Consult the datasheet for your receiver to
* be sure though; some receivers may have different pin assignments.
*
* The receiver is initialized by constructing an instance of the
* IRreceiver class:
*
* \code
* IRreceiver ir;
* \endcode
*
* By default, interrupt 0 on pin D2 is used. To change to another interrupt,
* pass its number to the constructor:
*
* \code
* IRreceiver ir(1); // Interrupt 1 on pin D3
* \endcode
*
* Currently this class can only handle a single instance of IRreceiver being
* active in the application. It isn't possible to have separate IRreceiver
* instances on different pins. Usually this won't be a problem because
* the same receiver can process inputs from multiple remotes.
*
* The application retrieves incoming infrared commands by calling the
* command() function. The return value indicates the type of command:
*
* \code
* void loop() {
* switch (ir.command()) {
* case RC5_0: case RC5_1: case RC5_2: case RC5_3: case RC5_4:
* case RC5_5: case RC5_6: case RC5_7: case RC5_8: case RC5_9:
* // Process a digit
* ...
* break;
*
* case RC5_ERASE:
* // Backspace/erase last digit.
* ...
* break;
*
* case RC5_STANDBY:
* // Power on/off button.
* ...
* break;
* }
* }
* \endcode
*
* If the command is an auto-repeat of a previous button press, then the
* \ref AUTO_REPEAT flag will be set in the value returned from command().
* The application can choose to ignore all auto-repeats, process all
* auto-repeats, or choose which button to auto-repeat based on its code:
*
* \code
* void loop() {
* switch (ir.command()) {
* case RC5_INC_VOLUME:
* case IRreceiver::AUTO_REPEAT | RC5_INC_VOLUME:
* // Volume increase button pressed or held.
* ...
* break;
*
* case RC5_DEC_VOLUME:
* case IRreceiver::AUTO_REPEAT | RC5_DEC_VOLUME:
* // Volume decrease button pressed or held.
* ...
* break;
*
* case RC5_MUTE:
* // Mute button (ignore auto-repeat).
* ...
* break;
* }
* }
* \endcode
*
* By default, command codes will be generated for every type of RC-5 remote
* control, be it a TV, VCR, CD player, or something else. The application
* can distinguish between the remote controls using system(); noting that
* command() must be called before system() for the system value to be valid.
* For example, the following code could be used in a two-player video game
* where the first player's remote is configured as a TV and the second
* player's remote is configured as a VCR:
*
* \code
* void loop() {
* int cmd = ir.command();
* int sys = ir.system();
* if (sys == RC5_SYS_TV)
* player1(cmd);
* else if (sys == RC5_SYS_VCR)
* player2(cmd);
*
* ...
* }
* \endcode
*
* If the application only cares about a single system and wishes to ignore
* all other systems, it can configure a system filter at startup:
*
* \code
* IRreceiver ir;
*
* void setup() {
* ir.setSystemFilter(RC5_SYS_VCR);
* }
* \endcode
*
* The complete list of RC-5 system numbers and command codes is given in
* the RC5.h header file.
*
* \sa \ref ir_dumpir "DumpIR Example"
*/
static IRreceiver *receiver = 0;
void _IR_receive_interrupt(void)
{
receiver->handleInterrupt();
}
/**
* \var IRreceiver::AUTO_REPEAT
* \brief Flag that is added to the output of command() when the command
* is an auto-repeated button press rather than the original button press.
*/
/**
* \brief Constructs a new infrared remote control receiver that is attached
* to \a interruptNumber.
*/
IRreceiver::IRreceiver(int interruptNumber)
: _system(0)
, _systemFilter(-1)
, started(false)
, halfChange(false)
, lastChange(0)
, bits(0)
, bitCount(0)
, buffer(0)
, lastBuffer(0)
{
switch (interruptNumber) {
case 0: default: pin = 2; break;
case 1: pin = 3; break;
case 2: pin = 21; break; // Arduino Mega only
case 3: pin = 20; break; // Arduino Mega only
case 4: pin = 19; break; // Arduino Mega only
case 5: pin = 18; break; // Arduino Mega only
}
receiver = this;
attachInterrupt(interruptNumber, _IR_receive_interrupt, CHANGE);
}
/**
* \brief Returns the next command from the remote control.
*
* Returns -1 if there is no new command, or the number between 0 and 127
* corresponding to the command. If the command is an auto-repeat button
* press rather than an original button press, then the \ref AUTO_REPEAT
* flag will be set.
*
* The companion function system() will return the system number for the
* command indicating whether the command is for a TV, VCR, CD player, etc.
* By default, all systems are reported; use setSystemFilter() to filter
* out commands from all but a specific system.
*
* The next call to command() will return -1 or the code for the next
* button press.
*
* The header file <tt>RC5.h</tt> contains a list of command codes for
* common remote controls.
*
* \sa system(), setSystemFilter()
*/
int IRreceiver::command()
{
unsigned buf;
// Read the last-delivered sequence from the buffer and clear it.
cli();
buf = buffer;
buffer = 0;
sei();
// Bail out if no sequence or it is not for us.
if (!buf) {
_system = -1;
return -1;
}
if (_systemFilter != -1) {
if (((buf >> 6) & 0x1F) != _systemFilter) {
_system = -1;
return -1;
}
}
// Extract the command.
int cmd = buf & 0x3F;
if ((buf & 0x1000) == 0)
cmd += 64;
// Is this a new command or an auto-repeat of the previous command?
// Bit 11 will toggle whenever a new button press is started.
if (lastBuffer == buf)
cmd += AUTO_REPEAT;
else
lastBuffer = buf;
_system = (buf >> 6) & 0x1F;
return cmd;
}
/**
* \fn int IRreceiver::system() const
* \brief Returns the system number of the previous command(), indicating
* whether the command was for a TV, VCR, CD player, etc.
*
* The return value from this function is valid only after a call to
* command(). The next call to command() will clear the system value,
* possibly to -1 if there is no new command.
*
* The header file <tt>RC5.h</tt> contains a list of system numbers for
* common remote controls.
*
* \sa command(), setSystemFilter()
*/
/**
* \fn int IRreceiver::systemFilter() const
* \brief Returns the system to filter commands against, or -1 if no
* filter is set.
*
* If this value is -1, then all received systems are returned via command()
* and system() irrespective of whether they are for a TV, VCR, CD player,
* or some other type of system. If this value is set to anything other
* than -1, then only commands for that system are returned via command().
*
* \sa setSystemFilter(), system(), command()
*/
/**
* \fn void IRreceiver::setSystemFilter(int system)
* \brief Sets the \a system to filter commands against, or -1 to turn
* off the system filter.
*
* If \a system is -1, then all received systems are returned via command()
* and system() irrespective of whether they are for a TV, VCR, CD player,
* or some other type of system. If \a system is set to anything other
* than -1, then only commands for that system are returned via command().
* For example:
*
* \code
* IRreceiver ir;
* ir.setSystemFilter(RC5_SYS_VCR);
* \endcode
*
* \sa systemFilter(), system(), command()
*/
// Number of microseconds that the signal is HIGH or LOW for
// indicating a bit. A 1 bit is transmitted as LOW for 889us
// followed by HIGH for 889us. A 0 bit is HIGH, then LOW.
#define IR_BIT_TIME 889
// Number of microseconds to detect a long gap in the coding
// corresponding to 2 time units HIGH or LOW. We actually check
// for at least 1.5 time units to allow for slight variations
// in timing on different remote controls.
#define IR_LONG_BIT_TIME (889 * 6 / 4)
// Maximum timeout for a single bit. If we don't see a rising edge
// within this time, then we have lost sync and need to restart.
#define IR_MAX_TIME (IR_BIT_TIME * 4)
// Protocol details from http://en.wikipedia.org/wiki/RC-5
void IRreceiver::handleInterrupt()
{
bool value = digitalRead(pin);
unsigned long currentTime = micros();
if (!value) {
// Rising edge (input is active-LOW)
if (started && (currentTime - lastChange) > IR_MAX_TIME) {
// Too long since the last received bit, so restart the process.
started = false;
}
if (started) {
// We recognize bits on the falling edges, so merely
// adjust the "changed at last half-cycle" flag.
if ((currentTime - lastChange) > IR_LONG_BIT_TIME) {
// Long time since last falling edge indicates that the
// next bit will definitely be a 1.
halfChange = true;
} else {
halfChange = !halfChange;
}
lastChange = currentTime;
} else {
// Encountered the start bit - start receiving up to 14 bits.
lastChange = currentTime;
started = true;
halfChange = true;
bits = 0;
bitCount = 14;
}
} else if (started) {
// Falling edge
if ((currentTime - lastChange) > IR_LONG_BIT_TIME) {
// Long time since last rise indicates 1 followed by 0.
bits = (bits << 2) | 0x02;
--bitCount;
halfChange = true;
} else if (halfChange) {
// Rise was halfway through, so falling edge indicates a 1.
bits = (bits << 1) | 0x01;
halfChange = false;
} else {
// Rise was at the start, so falling edge indicates a 0.
bits <<= 1;
halfChange = true;
}
lastChange = currentTime;
--bitCount;
if (bitCount <= 0) {
// All 14 bits have been received, so deliver the value.
started = false;
buffer = bits;
}
}
}