1 | /*
|
2 | * Decoder for Pollin PFR-130 Temerature/Rain Gauge
|
3 | * (c)27.05.2016 W.Kracht
|
4 | *
|
5 | * The external unit is sending at 433,92 MHz with ASK modulation
|
6 | * recommended receiver: Geeetech 433Mhz Superheterodyne 3400RF @5V (data at pin 8 of Arduino Uno)
|
7 | *
|
8 | * 36 bit protocol send every 45 seconds with 7 repeats
|
9 | *
|
10 | * Sync: 435us high 8100us low
|
11 | * 0: 435us high 2130us low
|
12 | * 1: 435us high 3840us low
|
13 | *
|
14 | * S0000 0000 0011 1111 1111 2222 2222 2233 3333
|
15 | * S0123 4567 8901 2345 6789 0123 4567 8901 2345
|
16 | * |----|----|----|----|----|----|----|----|----
|
17 | * S0000 hhhh bsRR tttt tttt tttt rrrr rrrr xxxx
|
18 | *
|
19 | * S: Sync
|
20 | * 0: 4 bit 0
|
21 | * h: 4 bit house code changes on
|
22 | * b: 1 bit battery state 0 == OK
|
23 | * t: 1 bit force TX switch 1 == switch pressed
|
24 | * R: 2 bit MSB of rain value
|
25 | * t: 12 bit temperature *10 (eg: 23,1 send as 231)
|
26 | * r: 8 bit LSB of rain value
|
27 | * x: 4 bit checksum XOR of 8 nibbles send
|
28 | *
|
29 | * The rain value is a 10 bit and is the accumulated count of seesaw pulses since last transmission multiplied by 25
|
30 | * one pulse is 25 and 40 pulses are 1000
|
31 | */
|
32 |
|
33 | // data pin of receiver has to be connected to pin d8
|
34 |
|
35 | #include <Arduino.h>
|
36 |
|
37 | #define PULSE_MAX 500
|
38 | static char width_icp[PULSE_MAX];
|
39 | static int pulse_cnt = 0;
|
40 | static int ovfl_cnt = 0;
|
41 |
|
42 | static unsigned long last_msg = 0;
|
43 |
|
44 | // input capture interrupt vector
|
45 | ISR(TIMER1_CAPT_vect)
|
46 | {
|
47 | static unsigned int last_icr = 0;
|
48 | static unsigned int low_us = 0;
|
49 | static int cnt = 0;
|
50 |
|
51 | if (0 < pulse_cnt) return;
|
52 |
|
53 | // Capture is an external asynchronous event. Overflow and Capture may occur at the
|
54 | // same time, but we are already entering this ISR. Check for missed overflow first.
|
55 | if (ICR1H < 16 && (TIFR1 & _BV(TOV1)))
|
56 | {
|
57 | ovfl_cnt++; // do what ISR(TIMER1_OVF_vect) should have done ;)
|
58 | TIFR1 = _BV(TOV1); // clear overflow flag, because already handled here
|
59 | }
|
60 |
|
61 | // calculate total time in us using overflows and time difference
|
62 | unsigned long t = (((unsigned long) ovfl_cnt << 16) + ICR1 - last_icr + 4) >> 3;
|
63 | last_icr = ICR1;
|
64 | ovfl_cnt = 0;
|
65 |
|
66 | if (TCCR1B & _BV(ICES1)) // detection set to rising edge
|
67 | {
|
68 | PORTB |= _BV(PB5); // LED d13 on
|
69 | TCCR1B &= ~_BV(ICES1); // set detection to falling edge
|
70 | low_us += t;
|
71 | }
|
72 | else // detection of falling edge
|
73 | {
|
74 | PORTB &= ~_BV(PB5); // LED d13 off
|
75 | TCCR1B |= _BV(ICES1); // set detection to rising edge
|
76 |
|
77 | if (t > 860L) // detected valid high pulse
|
78 | {
|
79 | if (low_us > 16000L)
|
80 | {
|
81 | // sync detected
|
82 | width_icp[cnt] = 0;
|
83 | if (cnt >= 36 && cnt <= 37)
|
84 | {
|
85 | pulse_cnt = cnt;
|
86 | }
|
87 | cnt = 0;
|
88 | if (pulse_cnt <= 0)
|
89 | {
|
90 | width_icp[cnt] = 0;
|
91 | }
|
92 | }
|
93 | else if (cnt < PULSE_MAX - 1)
|
94 | {
|
95 | width_icp[cnt++] = low_us > 6000L ? '1' : '0';
|
96 | }
|
97 | low_us = 0;
|
98 | }
|
99 | else
|
100 | {
|
101 | low_us += t;
|
102 | }
|
103 | }
|
104 | }
|
105 |
|
106 | // timer overflow interrupt vector
|
107 | ISR(TIMER1_OVF_vect)
|
108 | {
|
109 | ovfl_cnt++;
|
110 | }
|
111 |
|
112 | bool decoder()
|
113 | {
|
114 | int i;
|
115 | unsigned long code = 0;
|
116 | unsigned long cod1 = 0;
|
117 | int chks = 0;
|
118 | int chk1 = 0;
|
119 |
|
120 | for (i = 0; i < 32; i++)
|
121 | {
|
122 | code <<= 1;
|
123 | code |= (width_icp[i] == '1');
|
124 | }
|
125 |
|
126 | for (; i < 36; i++)
|
127 | {
|
128 | chks <<= 1;
|
129 | chks |= (width_icp[i] == '1');
|
130 | }
|
131 |
|
132 | cod1 = code;
|
133 | for (i = 7; i >= 0; i--)
|
134 | {
|
135 | chk1 ^= (cod1 & 0xf);
|
136 | cod1 >>= 4;
|
137 | }
|
138 |
|
139 | if (chks == chk1)
|
140 | {
|
141 | int hcod = (code >> 24) & 0xff;
|
142 | int batt = (code >> 23) & 0x1;
|
143 | int txsw = (code >> 22) & 0x1;
|
144 | int rain = (code >> 12) & 0x300;
|
145 | int temp = ((code >> 4) & 0xfff0) / 16; // take care of sign
|
146 | rain |= (code >> 0) & 0xff;
|
147 |
|
148 | //if (hc == 6) // fiter if there is more than one device detected
|
149 | {
|
150 | unsigned long duration = millis() - last_msg;
|
151 | if (last_msg == 0)
|
152 | {
|
153 | last_msg = duration;
|
154 | }
|
155 | else if (duration > 30000)
|
156 | {
|
157 | last_msg = millis();
|
158 | Serial.print("last message: ");
|
159 | Serial.println(duration);
|
160 | }
|
161 |
|
162 | width_icp[36] = 0;
|
163 | Serial.print(&width_icp[0]);
|
164 | Serial.print(": h=");
|
165 | Serial.print(hcod, HEX);
|
166 | Serial.print(", b=");
|
167 | Serial.print(batt);
|
168 | Serial.print(", s=");
|
169 | Serial.print(txsw);
|
170 | Serial.print(", t=");
|
171 | Serial.print(temp);
|
172 | Serial.print(", r=");
|
173 | Serial.println(rain);
|
174 | }
|
175 | }
|
176 |
|
177 | return (chks == chk1);
|
178 | }
|
179 |
|
180 | // initialize timer1 for PWM
|
181 | void timer_init()
|
182 | {
|
183 | // initialize timer1 for input capture with noise canceler
|
184 | TCCR1A = 0; // timer1 running free
|
185 | TCCR1B = _BV(ICNC1); // input capture on falling edge with noise canceler
|
186 | TIMSK1 = _BV(TOIE1) | _BV(ICIE1); // enable ICP and OVF interrupts
|
187 | TCCR1B |= _BV(CS10); // start timer1 with 8 MHz
|
188 | }
|
189 |
|
190 | void setup() {
|
191 | digitalWrite(8, HIGH); // Pull Up for D8
|
192 | pinMode(13, OUTPUT); // LED pin D13 shows interupt activity
|
193 |
|
194 | timer_init(); // setup timer 1 for free running pulse witdth measurement
|
195 | Serial.begin(115200); // decoded values arde dumped to Serial
|
196 | sei(); // enable interrupts
|
197 |
|
198 | Serial.println("scanning");
|
199 | }
|
200 |
|
201 | void loop() {
|
202 | if (pulse_cnt > 0)
|
203 | {
|
204 | decoder();
|
205 | pulse_cnt = 0;
|
206 | }
|
207 | }
|