261 lines
8.0 KiB
C++
261 lines
8.0 KiB
C++
/*
|
|
* ReceiverTimingAnalysis.cpp
|
|
*
|
|
* This program enables the pin change interrupt at pin 3 and waits for NEC (or other Pulse-Distance-Coding) IR Signal.
|
|
* It measures the pulse and pause times of the incoming signal and computes some statistics for it.
|
|
*
|
|
* Observed values:
|
|
* Delta of each signal type is around 50 up to 100 and at low signals up to 200. TSOP is better, especially at low IR signal level.
|
|
* VS1838 Mark Excess -50 to +50 us
|
|
* TSOP31238 Mark Excess 0 to +50
|
|
*
|
|
*
|
|
* Copyright (C) 2019-2020 Armin Joachimsmeyer
|
|
* armin.joachimsmeyer@gmail.com
|
|
*
|
|
* This file is part of IRMP https://github.com/IRMP-org/IRMP.
|
|
* This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
|
|
*
|
|
* IRMP is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
* See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/gpl.html>.
|
|
*
|
|
*/
|
|
|
|
#include <Arduino.h>
|
|
|
|
#define IR_RECEIVE_PIN 2
|
|
//#define IR_RECEIVE_PIN 3
|
|
|
|
/*
|
|
* Helper macro for getting a macro definition as string
|
|
*/
|
|
#define STR_HELPER(x) #x
|
|
#define STR(x) STR_HELPER(x)
|
|
|
|
void measureTimingISR(void);
|
|
|
|
void setup()
|
|
{
|
|
pinMode(LED_BUILTIN, OUTPUT);
|
|
Serial.begin(115200);
|
|
#if defined(__AVR_ATmega32U4__) || defined(SERIAL_PORT_USBVIRTUAL) || defined(SERIAL_USB) /*stm32duino*/|| defined(USBCON) /*STM32_stm32*/|| defined(SERIALUSB_PID) || defined(ARDUINO_attiny3217)
|
|
delay(4000); // To be able to connect Serial monitor after reset or power up and before first print out. Do not wait for an attached Serial Monitor!
|
|
#endif
|
|
// Just to know which program is running on my Arduino
|
|
Serial.println(F("START " __FILE__ " from " __DATE__));
|
|
|
|
#if defined(EICRA) && defined(EIFR) && defined(EIMSK)
|
|
# if (IR_RECEIVE_PIN == 2)
|
|
EICRA |= _BV(ISC00); // interrupt on any logical change
|
|
EIFR |= _BV(INTF0); // clear interrupt bit
|
|
EIMSK |= _BV(INT0); // enable interrupt on next change
|
|
# elif (IR_RECEIVE_PIN == 3)
|
|
EICRA |= _BV(ISC10); // enable interrupt on pin3 on both edges for ATmega328
|
|
EIFR |= _BV(INTF1); // clear interrupt bit
|
|
EIMSK |= _BV(INT1); // enable interrupt on next change
|
|
# endif
|
|
#else
|
|
attachInterrupt(digitalPinToInterrupt(IR_RECEIVE_PIN), measureTimingISR, CHANGE);
|
|
#endif
|
|
Serial.println(F("Ready to analyze NEC IR signal at pin " STR(IR_RECEIVE_PIN)));
|
|
Serial.println();
|
|
}
|
|
|
|
uint8_t ISREdgeCounter = 0;
|
|
volatile uint32_t LastMicros;
|
|
struct timingStruct
|
|
{
|
|
uint16_t minimum;
|
|
uint8_t indexOfMinimum;
|
|
uint16_t maximum;
|
|
uint8_t indexOfMaximum;
|
|
uint16_t average;
|
|
|
|
uint16_t SumForAverage;
|
|
uint8_t SampleCount;
|
|
// uint8_t LastPrintedCount;
|
|
};
|
|
|
|
struct timingStruct Mark;
|
|
struct timingStruct ShortSpace;
|
|
struct timingStruct LongSpace;
|
|
|
|
/*
|
|
* Compute minimum, maximum and average
|
|
*/
|
|
void processTmingValue(struct timingStruct *aTimingStruct, uint16_t aValue)
|
|
{
|
|
if (aTimingStruct->SampleCount == 0)
|
|
{
|
|
// initialize values
|
|
aTimingStruct->minimum = UINT16_MAX;
|
|
aTimingStruct->maximum = 0;
|
|
aTimingStruct->SumForAverage = 0;
|
|
}
|
|
|
|
if (aTimingStruct->minimum > aValue)
|
|
{
|
|
aTimingStruct->minimum = aValue;
|
|
aTimingStruct->indexOfMinimum = aTimingStruct->SampleCount;
|
|
}
|
|
if (aTimingStruct->maximum < aValue)
|
|
{
|
|
aTimingStruct->maximum = aValue;
|
|
aTimingStruct->indexOfMaximum = aTimingStruct->SampleCount;
|
|
}
|
|
|
|
aTimingStruct->SampleCount++;
|
|
aTimingStruct->SumForAverage += aValue;
|
|
aTimingStruct->average = (aTimingStruct->SumForAverage + (aTimingStruct->SampleCount / 2)) / aTimingStruct->SampleCount;
|
|
|
|
}
|
|
|
|
void printTimingValues(struct timingStruct *aTimingStruct, const char *aCaption)
|
|
{
|
|
// if (aTimingStruct->LastPrintedCount != aTimingStruct->SampleCount)
|
|
// {
|
|
// aTimingStruct->LastPrintedCount = aTimingStruct->SampleCount;
|
|
Serial.print(aCaption);
|
|
Serial.print(F(": SampleCount="));
|
|
Serial.print(aTimingStruct->SampleCount);
|
|
Serial.print(F(" Minimum="));
|
|
Serial.print(aTimingStruct->minimum);
|
|
Serial.print(F(" @"));
|
|
Serial.print(aTimingStruct->indexOfMinimum);
|
|
Serial.print(F(" Maximum="));
|
|
Serial.print(aTimingStruct->maximum);
|
|
Serial.print(F(" @"));
|
|
Serial.print(aTimingStruct->indexOfMaximum);
|
|
Serial.print(F(" Delta="));
|
|
Serial.print(aTimingStruct->maximum - aTimingStruct->minimum);
|
|
Serial.print(F(" Average="));
|
|
Serial.print(aTimingStruct->average);
|
|
|
|
Serial.println();
|
|
// }
|
|
}
|
|
|
|
void loop()
|
|
{
|
|
if (Mark.SampleCount >= 32)
|
|
{
|
|
/*
|
|
* This check enables statistics for longer protocols like Kaseikyo/Panasonics
|
|
*/
|
|
#if !defined(ARDUINO_ARCH_MBED)
|
|
noInterrupts();
|
|
#endif
|
|
uint32_t tLastMicros = LastMicros;
|
|
#if !defined(ARDUINO_ARCH_MBED)
|
|
interrupts();
|
|
#endif
|
|
uint32_t tMicrosDelta = micros() - tLastMicros;
|
|
|
|
if (tMicrosDelta > 10000)
|
|
{
|
|
// NEC signal ended just now
|
|
Serial.println();
|
|
printTimingValues(&Mark, "Mark ");
|
|
printTimingValues(&ShortSpace, "ShortSpace");
|
|
printTimingValues(&LongSpace, "LongSpace ");
|
|
|
|
/*
|
|
* Print analysis of mark and short spaces
|
|
*/
|
|
Serial.println(F("Analysis :"));
|
|
Serial.print(F(" (Average of mark + short space)/2 = "));
|
|
int16_t MarkAndShortSpaceAverage = (Mark.average + ShortSpace.average) / 2;
|
|
Serial.print(MarkAndShortSpaceAverage);
|
|
Serial.print(F(" us\r\n Delta (to NEC standard 560) = "));
|
|
Serial.print(MarkAndShortSpaceAverage - 560);
|
|
Serial.print(F("us\r\n MARK_EXCESS_MICROS = (Average of mark - Average of mark and short space) = "));
|
|
Serial.print((int16_t) Mark.average - MarkAndShortSpaceAverage);
|
|
Serial.print(F("us"));
|
|
Serial.println();
|
|
Serial.println();
|
|
|
|
Mark.SampleCount = 0; // used as flag for not printing the results more than once
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The interrupt handler.
|
|
* Just add to the appropriate timing structure.
|
|
*/
|
|
#if defined(ESP8266) || defined(ESP32)
|
|
void IRAM_ATTR measureTimingISR()
|
|
#else
|
|
# if defined(EICRA) && defined(EIFR) && defined(EIMSK)
|
|
# if (IR_RECEIVE_PIN == 2)
|
|
ISR(INT0_vect)
|
|
# elif (IR_RECEIVE_PIN == 3)
|
|
ISR(INT1_vect)
|
|
# endif
|
|
# else
|
|
void measureTimingISR()
|
|
# endif
|
|
#endif
|
|
{
|
|
uint32_t tMicros = micros();
|
|
uint32_t tMicrosDelta = tMicros - LastMicros;
|
|
LastMicros = tMicros;
|
|
/*
|
|
* read level and give feedback
|
|
*/
|
|
uint8_t tInputLevel = digitalRead(IR_RECEIVE_PIN);
|
|
digitalWrite(LED_BUILTIN, !tInputLevel);
|
|
|
|
if (tMicrosDelta > 10000)
|
|
{
|
|
// gap > 10 ms detected, reset counter to first detected edge and initialize timing structures
|
|
ISREdgeCounter = 1;
|
|
LongSpace.SampleCount = 0;
|
|
ShortSpace.SampleCount = 0;
|
|
Mark.SampleCount = 0;
|
|
}
|
|
else
|
|
{
|
|
ISREdgeCounter++;
|
|
}
|
|
|
|
/*
|
|
* Skip header mark and space and first bit mark and space
|
|
*/
|
|
if (ISREdgeCounter > 4)
|
|
{
|
|
if (tInputLevel != LOW)
|
|
{
|
|
// Mark ended
|
|
processTmingValue(&Mark, tMicrosDelta);
|
|
// Serial.print('M');
|
|
}
|
|
else
|
|
{
|
|
// Space ended
|
|
if (tMicrosDelta > 1000)
|
|
{
|
|
// long space - logical 1
|
|
processTmingValue(&LongSpace, tMicrosDelta);
|
|
Serial.print('1');
|
|
}
|
|
else
|
|
{
|
|
// short space - logical 0
|
|
processTmingValue(&ShortSpace, tMicrosDelta);
|
|
Serial.print('0');
|
|
}
|
|
}
|
|
}
|
|
}
|