You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

211 lines
9.7 KiB

/* IRrecvBase.cpp
* Part of IRLib Library for Arduino receiving, decoding, and sending
* infrared signals. See COPYRIGHT.txt and LICENSE.txt for more information.
*
* This module contains the base class classes for receiving IR signals. You will not create
* instances of this class, rather it is used as a base class for the 3 different
* receiver classes. These are the original IRrecv which uses a 50us timer interrupt.
* The IRrecvPCI version that uses pin change interrupts and IRrecvLoop which uses no timers
* or interrupts. Each of these has its own .h and .cpp file.
*
* The IRrecvLoop files are in this folder. However the other two receivers in their own
* separate libraries. That is because their interrupt service routines can conflict
* with other ISRs. The ISRs conflict would occur even if you did not create an instance
* of their receiver classes. Similarly the frequency detection receiver which also uses
* interrupts is also in a separate folder IRLibFreq.
*/
#include "IRLibRecvBase.h"
#include "IRLibHardware.h"
/* This structure contains all of the global variables used by the ISRs to communicate
* with the receiver and decoder objects. You cannot pass parameters to an ISR so
* these values must be global. The fields are defined in IRLibRecvBase.h
*/
recvGlobal_t recvGlobal;
// The base constructor receives the pin number
IRrecvBase::IRrecvBase(uint8_t recvPin) {
recvGlobal.recvPin = recvPin;
init();
}
// Initialization common to all three receiver classes
void IRrecvBase::init(void) {
//These first two lines would normally be done by the decoder
//however in rare circumstances there is no decoder.
recvGlobal.decoderWantsData=false; //turned on by enableIRIn.
recvGlobal.decodeBuffer=recvGlobal.recvBuffer;//default buffer
recvGlobal.enableAutoResume=false;
recvGlobal.frameTimeout=DEFAULT_FRAME_TIMEOUT;
recvGlobal.frameTimeoutTicks=recvGlobal.frameTimeout/USEC_PER_TICK;
markExcess=DEFAULT_MARK_EXCESS;
recvGlobal.newDataAvailable=false;
recvGlobal.enableBlinkLED=false;
recvGlobal.currentState=STATE_FINISHED;//i.e. Not running.
}
// Turn on or off a blinking LED which indicates signal received. Usually pin 13.
void blink13(bool enableBlinkLED){
#if (BLINKLED>0)
pinMode(BLINKLED,OUTPUT);
recvGlobal.enableBlinkLED=enableBlinkLED;
#endif
}
/* Any receiver class must implement a getResults method that will return true when
* a complete frame of data has been received. When your getResults determines that
* the frame is complete, it must guarantee that there will be no further changes to
* the data in the buffer or the length value. It can do that by either disabling
* interrupts or putting the ISR in a state that ensures it will not
* change those values. Then it must then call IRrecvBase::getResults. This base method
* will then will perform some math on the values and copy them to the decodeBuffer.
* Some receivers provide results in recvBuffer measured in ticks of some number of
* microseconds while others return results in actual microseconds. If you use ticks then
* you should pass a multiplier value in timePerTicks.
* NOTE: Only call the base method if newDataAvailable is true.
*/
bool IRrecvBase::getResults(const uint16_t timePerTick) {
//Conceptually the loop below copies data from the receiver buffer to the decode buffer
//while performing some math. In some instances, the source and destination are the same.
//The decoder has already set up decodeBuffer to point to the proper destination.
//Here we need to figure out the source. If didAutoResume is true then the ISR has already
//copied the data to decodeBuffer so it is both source and destination. In all
//other circumstances the source is always recvGlobal.recvBuffer.
recvGlobal.newDataAvailable=false;
volatile uint16_t *Source;
//DEBUG_VALUE("recvGlobal.didAutoResume",recvGlobal.didAutoResume);
if(recvGlobal.didAutoResume) {
Source=recvGlobal.decodeBuffer;
recvGlobal.didAutoResume=false;
} else {
Source=recvGlobal.recvBuffer;
recvGlobal.decodeLength=recvGlobal.recvLength;//if auto resumed, was already copied
}
//If the receiver counts time intervals rather than actual microseconds we will multiply
//the data by timePerTick. It also adjusts the data by adding or subtracting the
//markExcess value. See the documentation on markExcess for details.
for(uint8_t i=0; i<recvGlobal.decodeLength; i++) {
recvGlobal.decodeBuffer[i]=Source[i]*timePerTick + ( (i % 2)? -markExcess:markExcess);
}
//Now that the decoder has its data it doesn't want any more until it tells you.
//It will do so by calling enableIRIn.
recvGlobal.decoderWantsData=false;
return true;
}
/* The user calls enableIRIn each time it is ready to receive data. Previously
* this was only used to initialize the receiver once and subsequent calls were made
* to a method called "resume". However it was confusing because you had to call
* enableIRIn after a send because any sending would disable IRIn. It's simpler just to
* always use enableIRIn even though parts of it are slightly redundant.
* The interrupt driven receivers call this before enabling interrupts.
* See the comments on IRrecv::enableIRIn() in IRLibRecv.cpp regarding auto resume.
*/
void IRrecvBase::enableIRIn(void) {
//some IR receiver datesheets recommend pull-up resistors
pinMode(recvGlobal.recvPin, INPUT_PULLUP);
recvGlobal.recvLength = 0;
recvGlobal.currentState = STATE_READY_TO_BEGIN;
IRLib_didIROut=false;//We reinitialized so reset until somebody does more output.
}
/* Even when not receiving data or waiting to receive data, the ISR may remain active
* but remains in a do-nothing state. If the user wants to truly shut down the ISR
* they can call this method. The derived method should disable the ISR and then call
* this base method to the turn everything off.
*/
void IRrecvBase::disableIRIn(void) {
recvGlobal.decoderWantsData=false;
recvGlobal.didAutoResume=false;
recvGlobal.currentState=STATE_FINISHED;//i.e. Not running.
}
/*
* Normally recvGlobal.decodeBuffer points to recvGlobal.recvBuffer and therefore
* decoding uses the same buffer as receiving. However you may want to resume
* receiving while still decoding. To do so must specify a separate buffer for decoding.
* You will declare the buffer as "uint16_t myBuffer[RECV_BUF_LENGHT];" in your sketch
* then pass its address using the method below. Then IRrecvBase::getResults() will copy
* timing values from its buffer to yours. The receiver will then automatically resume.
* The receiver will not overwrite your buffer unless you have called enableIRIn()
* to tell it that you have finished your decoding. In other words auto resume will only
* occur once until you again call enableIRIn().
*/
void IRrecvBase::enableAutoResume(uint16_t *P){
recvGlobal.decodeBuffer=(volatile uint16_t*)P;
recvGlobal.enableAutoResume=true;
};
// This had to be a method so that IRrecv::setFrameTimeout can compute
// frameTimeoutTicks.
void IRrecvBase::setFrameTimeout(uint16_t frameTimeout) {
recvGlobal.frameTimeout=frameTimeout;
}
/*********************
*
* The remaining functions below are not part of any class or object. They are global
* so they can be called by the ISRs or for other reasons are not really tied to any
* class or object.
*
*********************/
/*
* This function is called by both the 50us and PCI ISR in one of two circumstances:
* 1) The SPACE was long enough that we are sure the frame is over and ready to process.
* 2) The buffer overflowed we have to quit. The parameter is for debugging purposes only.
*/
void IRLib_IRrecvComplete(uint8_t Reason) {
// Here we are finished. Let the world know there is new data available.
recvGlobal.newDataAvailable=true;
recvGlobal.currentState=STATE_FINISHED;//this essentially pauses the receiver ISR
//Now we need to see if we want to auto resume. We can only do that if it is enabled and
//if the user is finished using the buffer from the previous capture and wants more data.
//DEBUG_VALUE ("Reason completed", Reason)
if (recvGlobal.enableAutoResume && recvGlobal.decoderWantsData) {
//Here we do the actual auto resume. We will copy the data using memcpy because it
//should be very quick. Any calculations will be handled by the getResults method but
//not here.
memcpy((void *)recvGlobal.decodeBuffer, (const void*)recvGlobal.recvBuffer,recvGlobal.recvLength*sizeof(uint16_t));
recvGlobal.decodeLength=recvGlobal.recvLength;
// We have just copied the data to the decoder so it's not going to want more until
// it tells us that it is ready for more.
recvGlobal.decoderWantsData=false;
// Tell getResults that we auto resumed therefore the data has been copied but
// still needs the math done.
recvGlobal.didAutoResume=true;
// Now we need to reset the index to the beginning and restart the state machine.
recvGlobal.recvLength=0;
//While we were doing the copy, the 50 us interrupt continued but the state machine
//was paused in the STATE_FINISHED. Now we actually turn it back on till you get to
//actually receive data.
recvGlobal.currentState= STATE_READY_TO_BEGIN;
}
}
/* If your hardware is set up to do both output and input but your particular sketch
* doesn't do any output, this method will ensure that your output pin is low
* and doesn't turn on your IR LED or any output circuit.
*/
void IRLib_NoOutput (void) {
#if defined(IR_SEND_PWM_PIN)
pinMode(IR_SEND_PWM_PIN, OUTPUT);
digitalWrite(IR_SEND_PWM_PIN, LOW); // When not sending PWM, we want it low
#endif
}
/* Do the actual blinking off and on of the indicator LED. Called by the various
* receiver ISRs
*/
void IRLib_doBlink(void) {
if (recvGlobal.enableBlinkLED) {
if(recvGlobal.recvLength & 1) {
BLINKLED_ON(); // turn pin 13 LED on
}
else {
BLINKLED_OFF(); // turn pin 13 LED off
}
}
}