-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSoftModem.cpp
More file actions
395 lines (336 loc) · 12 KB
/
SoftModem.cpp
File metadata and controls
395 lines (336 loc) · 12 KB
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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
#include "SoftModem.h"
#define TX_PIN (3)
#define RX_PIN1 (6) // AIN0
#define RX_PIN2 (7) // AIN1
SoftModem *SoftModem::activeObject = 0;
SoftModem::SoftModem() {
}
SoftModem::~SoftModem() {
end();
}
#if F_CPU == 16000000
#if SOFT_MODEM_BAUD_RATE <= 126
#define TIMER_CLOCK_SELECT (7)
#define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(1024))
#elif SOFT_MODEM_BAUD_RATE <= 315
#define TIMER_CLOCK_SELECT (6)
#define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(256))
#elif SOFT_MODEM_BAUD_RATE <= 630
#define TIMER_CLOCK_SELECT (5)
#define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(128))
#elif SOFT_MODEM_BAUD_RATE <= 1225
#define TIMER_CLOCK_SELECT (4)
#define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(64))
#else
#define TIMER_CLOCK_SELECT (3)
#define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(32))
#endif
#else
#if SOFT_MODEM_BAUD_RATE <= 126
#define TIMER_CLOCK_SELECT (6)
#define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(256))
#elif SOFT_MODEM_BAUD_RATE <= 315
#define TIMER_CLOCK_SELECT (5)
#define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(128))
#elif SOFT_MODEM_BAUD_RATE <= 630
#define TIMER_CLOCK_SELECT (4)
#define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(64))
#else
#define TIMER_CLOCK_SELECT (3)
#define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(32))
#endif
#endif
//time length of a bit in transmission (microseconds unit) - baud rate: bit per second
#define BIT_PERIOD (1000000/SOFT_MODEM_BAUD_RATE)
//period of high frequence waveform (in microseconds) - time length of a full-cycle
#define HIGH_FREQ_MICROS (1000000/SOFT_MODEM_HIGH_FREQ)
//period of low frequence waveform (in microseconds) - time length of a full-cycle
#define LOW_FREQ_MICROS (1000000/SOFT_MODEM_LOW_FREQ)
//number of full-period (full-cycle) hight frequency waveform in 1 HIGH bit
#define HIGH_FREQ_CNT (BIT_PERIOD/HIGH_FREQ_MICROS)
//number of full-period (full-cycle) low frequency waveform in 1 LOW bit
#define LOW_FREQ_CNT (BIT_PERIOD/LOW_FREQ_MICROS)
//number of bits transfer in 40ms
#define MAX_CARRIR_BITS (40000/BIT_PERIOD) // 40ms
//number of timer count in 1 bit period
#define TCNT_BIT_PERIOD (BIT_PERIOD/MICROS_PER_TIMER_COUNT)
//number of timer count in 1 full-cycle high frequency waveform
#define TCNT_HIGH_FREQ (HIGH_FREQ_MICROS/MICROS_PER_TIMER_COUNT)
//number of timer count in 1 full-cycle low frequency waveform
#define TCNT_LOW_FREQ (LOW_FREQ_MICROS/MICROS_PER_TIMER_COUNT)
//lower threshold for timer count for high freq
#define TCNT_HIGH_TH_L (TCNT_HIGH_FREQ * 0.80)
//upper threshold for timer count for high freq
#define TCNT_HIGH_TH_H (TCNT_HIGH_FREQ * 1.15)
//lower threshold for timer count for low freq
#define TCNT_LOW_TH_L (TCNT_LOW_FREQ * 0.85)
//upper threshold for timer count for low freq
#define TCNT_LOW_TH_H (TCNT_LOW_FREQ * 1.20)
#if SOFT_MODEM_DEBUG_ENABLE
static volatile uint8_t *_portLEDReg;
static uint8_t _portLEDMask;
#endif
enum { START_BIT = 0, DATA_BIT = 8, STOP_BIT = 9, INACTIVE = 0xff };
void SoftModem::begin(void)
{
//set received pin RX_PIN1 to INPUT mode, set it to LOW
pinMode(RX_PIN1, INPUT);
digitalWrite(RX_PIN1, LOW);
//set received pin RX_PIN2 to INPUT mode, set it to LOW
pinMode(RX_PIN2, INPUT);
digitalWrite(RX_PIN2, LOW);
//set transmit pin TX_PIN to OUTPUT mode, set it to LOW
pinMode(TX_PIN, OUTPUT);
digitalWrite(TX_PIN, LOW);
//find out Port Register for transmit pin TX_PIN
// digitalPinToPort returns name of the port that TX_PIN belongs
// portOutputRegister returns register that used to control port that TX_PIN belongs
// which in turn control TX_PIN value
_txPortReg = portOutputRegister(digitalPinToPort(TX_PIN));
//find the bitmask used to set transmit pin TX_PIN in port register
_txPortMask = digitalPinToBitMask(TX_PIN);
#if SOFT_MODEM_DEBUG_ENABLE
_portLEDReg = portOutputRegister(digitalPinToPort(13));
_portLEDMask = digitalPinToBitMask(13);
pinMode(13, OUTPUT);
#endif
_recvStat = INACTIVE;
//reset ring buffer of received buffer
_recvBufferHead = _recvBufferTail = 0;
SoftModem::activeObject = this;
//assign _lastTCNT to timer counter register TCNT2, TCNT2 increase by 1 for each timer clock,
// time clock can be set by prescale in timer counter control register TCCR2A & TCCR2B --> refer Datasheet
_lastTCNT = TCNT2;
_lastDiff = _lowCount = _highCount = 0;
//set all bit in timer counter control register TCCR2A to 0
TCCR2A = 0;
//set timer counter contrl register TCCR2B to TIMER_CLOCK_SELECT
// TIMER_CLOCK_SELECT will decide prescale for counting --> Refer Datasheet ATMEGA328P in p158
// TIMER_CLOCK_SELECT is defined above base on F_CPU (CPU frequency) and baud rate
// in case of Arduino Uno, F_CPU = 16MHz, if we set baud rate to 1225, then we have
// #define TIMER_CLOCK_SELECT (4)
// #define MICROS_PER_TIMER_COUNT (clockCyclesToMicroseconds(64))
// according to Datasheet, if TCCR2B is set to 4 (0b00000100), then it is prescaled 64 times (64 clocks for +1 count)
// that is the reason that MICROS_PER_TIMER_COUNT = clockCyclesToMicroseconds(64)
// F_CPU = 16MHz --> 16 clk is 1us
// Prescale = 64 --> 64 clk is 1 count
// --> each increment of timer counter equal to 64/16 = 4us pass --> clockCyclesToMicroseconds(64)
TCCR2B = TIMER_CLOCK_SELECT;
// ACSR: Analog Comparator Control and Status Register (Datasheet p240)
// ACIE: name of bit in ACSR which control Analog Comparator Interrupt Enable
// ACIS1: name of bit in ACSR which is Analog Comparator Interrupt Mode Select.
// This bit determine which comparator events that trigger the Analog Comparator interrupt.
// here we set ACSR = 0b00001010 --> Comparator Interrupt on Falling Output Edge.
ACSR = _BV(ACIE) | _BV(ACIS1);
// digital port off --> save power consumption, only need analog input value (Datasheet p241)
DIDR1 = _BV(AIN1D) | _BV(AIN0D);
}
void SoftModem::end(void)
{
ACSR &= ~(_BV(ACIE));
TIMSK2 &= ~(_BV(OCIE2A));
DIDR1 &= ~(_BV(AIN1D) | _BV(AIN0D));
SoftModem::activeObject = 0;
}
void SoftModem::demodulate(void)
{
uint8_t t = TCNT2;
uint8_t diff;
if(TIFR2 & _BV(TOV2)){
TIFR2 |= _BV(TOV2);
diff = (255 - _lastTCNT) + t + 1;
}
else{
diff = t - _lastTCNT;
}
if(diff < (uint8_t)(TCNT_HIGH_TH_L)) // Noise?
return;
_lastTCNT = t;
if(diff > (uint8_t)(TCNT_LOW_TH_H))
return;
// _lastDiff = (diff >> 1) + (diff >> 2) + (_lastDiff >> 2);
_lastDiff = diff;
if(_lastDiff >= (uint8_t)(TCNT_LOW_TH_L)){
_lowCount += _lastDiff;
if((_recvStat == INACTIVE) && (_lowCount >= (uint8_t)(TCNT_BIT_PERIOD * 0.5))){ // maybe Start-Bit
_recvStat = START_BIT;
_highCount = 0;
_recvBits = 0;
OCR2A = t + (uint8_t)(TCNT_BIT_PERIOD) - _lowCount; // 1 bit period after detected
TIFR2 |= _BV(OCF2A);
TIMSK2 |= _BV(OCIE2A);
}
}
else if(_lastDiff <= (uint8_t)(TCNT_HIGH_TH_H)){
_highCount += _lastDiff;
if((_recvStat == INACTIVE) && (_highCount >= (uint8_t)(TCNT_BIT_PERIOD))){
_lowCount = _highCount = 0;
}
}
}
ISR(ANALOG_COMP_vect)
{
SoftModem::activeObject->demodulate();
}
void SoftModem::recv(void)
{
uint8_t high;
if(_highCount > _lowCount){
if(_highCount >= (uint8_t)TCNT_BIT_PERIOD)
_highCount -= (uint8_t)TCNT_BIT_PERIOD;
else
_highCount = 0;
high = 0x80;
}
else{
if(_lowCount >= (uint8_t)TCNT_BIT_PERIOD)
_lowCount -= (uint8_t)TCNT_BIT_PERIOD;
else
_lowCount = 0;
high = 0x00;
}
if(_recvStat == START_BIT){ // Start bit
if(!high){
_recvStat++;
}else{
goto end_recv;
}
}
else if(_recvStat <= DATA_BIT) { // Data bits
_recvBits >>= 1;
_recvBits |= high;
_recvStat++;
}
else if(_recvStat == STOP_BIT){ // Stop bit
uint8_t new_tail = (_recvBufferTail + 1) & (SOFT_MODEM_RX_BUF_SIZE - 1);
if(new_tail != _recvBufferHead){
_recvBuffer[_recvBufferTail] = _recvBits;
_recvBufferTail = new_tail;
}
goto end_recv;
}
else{
end_recv:
_recvStat = INACTIVE;
TIMSK2 &= ~_BV(OCIE2A);
}
}
ISR(TIMER2_COMPA_vect)
{
OCR2A += (uint8_t)TCNT_BIT_PERIOD;
SoftModem::activeObject->recv();
#if SOFT_MODEM_DEBUG_ENABLE
*_portLEDReg ^= _portLEDMask;
#endif
}
int SoftModem::available()
{
return (_recvBufferTail + SOFT_MODEM_RX_BUF_SIZE - _recvBufferHead) & (SOFT_MODEM_RX_BUF_SIZE - 1);
}
int SoftModem::read()
{
if(_recvBufferHead == _recvBufferTail)
return -1;
int d = _recvBuffer[_recvBufferHead];
_recvBufferHead = (_recvBufferHead + 1) & (SOFT_MODEM_RX_BUF_SIZE - 1);
return d;
}
int SoftModem::peek()
{
if(_recvBufferHead == _recvBufferTail)
return -1;
return _recvBuffer[_recvBufferHead];
}
void SoftModem::flush()
{
_recvBufferHead = _recvBufferTail = 0;
}
void SoftModem::modulate(uint8_t b)
{
uint8_t cnt,tcnt,tcnt2;
if(b){ //if modulated bit is HIGH
//number of full-period (full-cycle) hight frequency waveform
// in 1 HIGH bit
cnt = (uint8_t)(HIGH_FREQ_CNT);
//timer count in half-cycle high freq waveform
tcnt2 = (uint8_t)(TCNT_HIGH_FREQ / 2);
//timer count in half-cycle high freq waveform
// we use substraction because it is neccessary to satisfy
// tcnt + tcnt2 == TCNT_HIGH_FREQ
tcnt = (uint8_t)(TCNT_HIGH_FREQ) - tcnt2;
}else{ //if modulated bit is LOW
cnt = (uint8_t)(LOW_FREQ_CNT);
tcnt2 = (uint8_t)(TCNT_LOW_FREQ / 2);
tcnt = (uint8_t)(TCNT_LOW_FREQ) - tcnt2;
}
do {
cnt--;
/*=== The first half-cycle waveform ===*/
{
//TCNT2 timer counter will be continously counting
// value in OCR2B will be continously compared to TCNT2
//set OCR2B to timer count number of first-half-cycle waveform
OCR2B += tcnt;
//set flag in TIFR2 (Timer Counter2 Interrupt Flag Register)
// this OCF2B bit in TIFR2 will be cleared after there is a match
// in comparing TCNT2 and OCR2B
TIFR2 |= _BV(OCF2B);
//loop until there is a match in comparing TCNT2 and OCR2B
// when OCR2B == TCNT2, bit OCF2B in TIFR2 will be cleard
while(!(TIFR2 & _BV(OCF2B)));
}
//When there is match in comparing OCR2B and TCNT2,
// invert output value of transmit output pin
*_txPortReg ^= _txPortMask;
/*=== The second half-cycle waveform ===*/
{
//set OCR2B to timer count number of first-half-cycle waveform
OCR2B += tcnt2;
//set flag in TIFR2 (Timer Counter2 Interrupt Flag Register)
// this OCF2B bit in TIFR2 will be cleared after there is a match
// in comparing TCNT2 and OCR2B
TIFR2 |= _BV(OCF2B);
//loop until there is a match in comparing TCNT2 and OCR2B
// when OCR2B == TCNT2, bit OCF2B in TIFR2 will be cleard
while(!(TIFR2 & _BV(OCF2B)));
}
//When there is match in comparing OCR2B and TCNT2,
// invert output value of transmit output pin
*_txPortReg ^= _txPortMask;
} while (cnt);
}
// Brief carrier tone before each transmission
// 1 start bit (LOW)
// 8 data bits, LSB first
// 1 stop bit (HIGH)
// ...
// 1 push bit (HIGH)
size_t SoftModem::write(const uint8_t *buffer, size_t size)
{
uint8_t cnt = ((micros() - _lastWriteTime) / BIT_PERIOD) + 1;
if(cnt > MAX_CARRIR_BITS)
cnt = MAX_CARRIR_BITS;
for(uint8_t i = 0; i<cnt; i++)
modulate(HIGH);
size_t n = size;
while (size--) {
uint8_t data = *buffer++;
modulate(LOW); // Start Bit
for(uint8_t mask = 1; mask; mask <<= 1){ // Data Bits
if(data & mask){
modulate(HIGH);
}
else{
modulate(LOW);
}
}
modulate(HIGH); // Stop Bit
}
modulate(HIGH); // Push Bit
_lastWriteTime = micros();
return n;
}
size_t SoftModem::write(uint8_t data)
{
return write(&data, 1);
}