-
Notifications
You must be signed in to change notification settings - Fork 2
/
IrReceiver.h
264 lines (233 loc) · 8.7 KB
/
IrReceiver.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
#ifndef IR_RECEIVER_H
#define IR_RECEIVER_H
#include "Arduino.h"
#include "StateMachine.h"
namespace IrReceiverUtils
{
using namespace StateMachineUtils;
enum ReceiverStateId
{
WAITING_FOR_PACKET, // Have not yet received automatic gain control (AGC) burst which signals the start of a code/repeat
RECEIVING_PACKET, // Have received the AGC burst and anywhere between 0 and 31 bits
RECEIVED_PACKET // Have received a full code (or a repeat burst). Waiting for result to be consumed
};
struct IrPacket
{
bool IsRepeat;
unsigned long Code;
};
// See https://www.sbprojects.net/knowledge/ir/nec.php
// Intervals are measured between signal falls
// i.e. our receiver does not care how long bursts are,
// it only cares about the time interval between them
// This greatly simplifies the state graph and allows
// supporting transmitters with non-standard burst intervals
unsigned long const ZERO_DURATION = 1125UL;
unsigned long const ONE_DURATION = 2250UL;
unsigned long const REPEAT_DURATION = 2810UL;
unsigned long const AGC_DURATION = 5060UL;
// Half-width of the timing precision window
unsigned long const HALF_WINDOW = 80UL;
byte const BITS_PER_CODE = 32;
bool const WithinWindow(unsigned long const testDuration, unsigned long const windowCentre)
{
return testDuration >= (windowCentre - HALF_WINDOW) && testDuration <= (windowCentre + HALF_WINDOW);
}
class WaitingForPacketState : public State<ReceiverStateId>
{
private:
volatile IrPacket & packet;
public:
WaitingForPacketState(volatile IrPacket & packet)
: packet(packet)
{ }
ReceiverStateId const Tick(unsigned long const deltaMicros)
{
if(WithinWindow(deltaMicros, REPEAT_DURATION))
{
packet.IsRepeat = true;
return RECEIVED_PACKET;
}
else if(WithinWindow(deltaMicros, AGC_DURATION))
{
return RECEIVING_PACKET;
}
else return WAITING_FOR_PACKET;
}
void OnEnterState() { }
};
class ReceivingPacketState : public State<ReceiverStateId>
{
private:
volatile IrPacket & packet;
byte bitsCaptured = 0;
public:
ReceivingPacketState(volatile IrPacket & packet)
: packet(packet)
{ }
ReceiverStateId const Tick(unsigned long const deltaMicros)
{
if (WithinWindow(deltaMicros, ZERO_DURATION))
{
packet.Code *= 2;
return (++bitsCaptured == BITS_PER_CODE) ? RECEIVED_PACKET : RECEIVING_PACKET;
}
else if (WithinWindow(deltaMicros, ONE_DURATION))
{
packet.Code *= 2;
packet.Code++;
return (++bitsCaptured == BITS_PER_CODE) ? RECEIVED_PACKET : RECEIVING_PACKET;
}
else
{
return WAITING_FOR_PACKET;
}
}
void OnEnterState()
{
packet.Code = 0UL;
packet.IsRepeat = false;
bitsCaptured = 0;
}
};
class ReceivedPacketState : public State<ReceiverStateId>
{
private:
volatile IrPacket const & packet;
volatile unsigned long & lastCode;
volatile bool & packetReady;
public:
ReceivedPacketState(
volatile IrPacket const & packet,
volatile unsigned long & lastCode,
volatile bool & packetReady)
: packet(packet)
, lastCode(lastCode)
, packetReady(packetReady)
{ }
ReceiverStateId const Tick(unsigned long const)
{
return RECEIVED_PACKET;
}
void OnEnterState()
{
if(!packet.IsRepeat) lastCode = packet.Code;
packetReady = true;
}
};
/**
* Interface that allows InputPinIrReceiver references to shed their template parameter
*/
class IrReceiver
{
public:
/**
* Attempt to read a data packet from the receiver
*
* @param outPacket On successful read, will contain packet data
*
* @returns True iff. there was a fully captured data packet
* that had not previously been read
*/
virtual bool TryGetPacket(IrPacket & outPacket) = 0;
bool TryGetPacket()
{
IrPacket packet;
return TryGetPacket(packet);
}
/**
* @returns The last code (non-repeat packet) captured by the receiver
* Returned value is not valid until at least one packet has been captured
*/
virtual volatile unsigned long GetLastCode() const = 0;
};
/**
* IR Receiver for NEC protocol IR data transmission
* Attach to an interrupt capable digital input pin
* which has a 38kHz IR demodulator (e.g. TSOP1838) connected
*
* This class does NOT buffer packets. Once a data packet has
* arrived, the receiver will ignore subsequent packets until
* one of the TryGetPacket overloads reads the packet
*/
template <int ReceiverPin> class InputPinIrReceiver :
private StateMachine<ReceiverStateId>,
public IrReceiver
{
private:
inline static InputPinIrReceiver<ReceiverPin> instance;
// These variables are written to inside the interrupt context,
// but can be read from the main program thread. Therefore,
// they must be marked volatile, so that the compiler does
// not naively cache them on the main thread.
volatile IrPacket packet;
volatile unsigned long lastCode;
volatile bool packetReady = false;
WaitingForPacketState waitingForPacketState;
ReceivingPacketState receivingPacketState;
ReceivedPacketState receivedPacketState;
static void handleSignalFall()
{
instance.Tick();
}
InputPinIrReceiver()
: StateMachine(WAITING_FOR_PACKET, &waitingForPacketState)
, waitingForPacketState(packet)
, receivingPacketState(packet)
, receivedPacketState(packet, lastCode, packetReady)
{ }
protected:
State<ReceiverStateId> * GetStateInstance(ReceiverStateId const stateIdentifier) const
{
switch(stateIdentifier)
{
case RECEIVING_PACKET: return &receivingPacketState;
case RECEIVED_PACKET: return &receivedPacketState;
case WAITING_FOR_PACKET:
default:
return &waitingForPacketState;
}
}
public:
/**
* Attach the receiver to the input pin via a pin interrupt
* It is the caller's responsibility to ensure that the provided
* pin is interrupt capable, configured as an input,
* and that the interrupt is free. No validation is performed
*
* @param inverted Should be true if the attached receiver inverts
* the signal when it is demodulated (true for most TSOPxx38 modules)
*
* @returns The receiver instance
*/
static IrReceiver& Attach(bool const inverted)
{
attachInterrupt(
digitalPinToInterrupt(ReceiverPin),
handleSignalFall,
inverted ? RISING : FALLING);
return instance;
}
static void Detach()
{
detachInterrupt(digitalPinToInterrupt(ReceiverPin));
}
bool TryGetPacket(IrPacket & outPacket)
{
if (packetReady)
{
outPacket.Code = packet.Code;
outPacket.IsRepeat = packet.IsRepeat;
SetState(WAITING_FOR_PACKET);
packetReady = false;
return true;
}
else return false;
}
volatile unsigned long GetLastCode() const
{
return lastCode;
}
};
}
#endif //IR_RECEIVER_H