j_external_pcint_library.ino example

Example J: Using External Interrupts.

Example J: Using External Interrupts

This is identical to example B, except that it uses the library EnableInterrupt to assign the pin change interrupt vector. This allows it to play nicely with any other libraries that try to assign functionality to the pin change interrupt vectors.

For this to work, you must remove the comment braces around #define SDI12_EXTERNAL_PCINT in the library and re-compile it.

1/**
2 * @example{lineno} j_external_pcint_library.ino
3 * @copyright Stroud Water Research Center
4 * @license This example is published under the BSD-3 license.
5 * @author Kevin M.Smith <SDI12@ethosengineering.org>
6 *
7 * @brief Example J: Using External Interrupts
8 *
9 * This is identical to example B, except that it uses the library
10 * [EnableInterrupt](https://github.com/GreyGnome/EnableInterrupt) to assign the pin
11 * change interrupt vector. This allows it to play nicely with any other libraries that
12 * try to assign functionality to the pin change interrupt vectors.
13 *
14 * For this to work, you must remove the comment braces around
15 * `#define SDI12_EXTERNAL_PCINT` in the library and re-compile it.
16 */
17
18#include <EnableInterrupt.h>
19#include <SDI12.h>
20
21#ifndef SDI12_DATA_PIN
22#define SDI12_DATA_PIN 7
23#endif
24#ifndef SDI12_POWER_PIN
25#define SDI12_POWER_PIN 22
26#endif
27
28/* connection information */
29uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */
30int8_t dataPin = SDI12_DATA_PIN; /*!< The pin of the SDI-12 data bus */
31int8_t powerPin = SDI12_POWER_PIN; /*!< The sensor power pin (or -1) */
32uint32_t wake_delay = 0; /*!< Extra time needed for the sensor to wake (0-100ms) */
33int8_t firstAddress = 0; /* The first address in the address space to check (0='0') */
34int8_t lastAddress = 61; /* The last address in the address space to check (61='z') */
35bool printIO = false;
36
37/** Define the SDI-12 bus */
38SDI12 mySDI12(dataPin);
39
40// keeps track of active addresses
41bool isActive[64];
42
43uint8_t numSensors = 0;
44
45/**
46 * @brief converts allowable address characters ('0'-'9', 'a'-'z', 'A'-'Z') to a
47 * decimal number between 0 and 61 (inclusive) to cover the 62 possible
48 * addresses.
49 */
50byte charToDec(char i) {
51 if ((i >= '0') && (i <= '9')) return i - '0';
52 if ((i >= 'a') && (i <= 'z')) return i - 'a' + 10;
53 if ((i >= 'A') && (i <= 'Z'))
54 return i - 'A' + 36;
55 else
56 return i;
57}
58
59/**
60 * @brief maps a decimal number between 0 and 61 (inclusive) to allowable
61 * address characters '0'-'9', 'a'-'z', 'A'-'Z',
62 *
63 * THIS METHOD IS UNUSED IN THIS EXAMPLE, BUT IT MAY BE HELPFUL.
64 */
65char decToChar(byte i) {
66 if (i < 10) return i + '0';
67 if ((i >= 10) && (i < 36)) return i + 'a' - 10;
68 if ((i >= 36) && (i <= 62))
69 return i + 'A' - 36;
70 else
71 return i;
72}
73
74/**
75 * @brief gets identification information from a sensor, and prints it to the serial
76 * port
77 *
78 * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'.
79 */
80void printInfo(char i) {
81 String command = "";
82 command += (char)i;
83 command += "I!";
84 mySDI12.sendCommand(command, wake_delay);
85 delay(30);
86
87 String sdiResponse = mySDI12.readStringUntil('\n');
88 sdiResponse.trim();
89 // allccccccccmmmmmmvvvxxx...xx<CR><LF>
90 Serial.print(sdiResponse.substring(0, 1)); // address
91 Serial.print(", ");
92 Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number
93 Serial.print(", ");
94 Serial.print(sdiResponse.substring(3, 11)); // vendor id
95 Serial.print(", ");
96 Serial.print(sdiResponse.substring(11, 17)); // sensor model
97 Serial.print(", ");
98 Serial.print(sdiResponse.substring(17, 20)); // sensor version
99 Serial.print(", ");
100 Serial.print(sdiResponse.substring(20)); // sensor id
101 Serial.print(", ");
102}
103
104bool getResults(char addr, int resultsExpected) {
105 uint8_t resultsReceived = 0;
106 uint8_t cmd_number = 0;
107 uint8_t cmd_retries = 0;
108
109 // When requesting data, the sensor sends back up to ~80 characters at a
110 // time to each data request. If it needs to return more results than can
111 // fit in the first data request (D0), we need to make additional requests
112 // (D1-9). Since this is a parent to all sensors, we're going to keep
113 // requesting data until we either get as many results as we expect or no
114 // more data is returned.
115 while (resultsReceived < resultsExpected && cmd_number <= 9 && cmd_retries < 5) {
116 bool gotResults = false;
117 uint8_t cmd_results = 0;
118 // Assemble the command based on how many commands we've already sent,
119 // starting with D0 and ending with D9
120 // SDI-12 command to get data [address][D][dataOption][!]
121 mySDI12.clearBuffer();
122 String command = "";
123 command += addr;
124 command += "D";
125 command += cmd_number;
126 command += "!";
127 mySDI12.sendCommand(command, wake_delay);
128 delay(30);
129 if (printIO) {
130 Serial.print(">>>");
131 Serial.println(command);
132 }
133
134 // Wait for the first few characters to arrive. The response from a data
135 // request should always have more than three characters
136 uint32_t start = millis();
137 while (mySDI12.available() < 3 && (millis() - start) < 1500) {}
138
139 // read the returned address to remove it from the buffer
140 char returnedAddress = mySDI12.read();
141 if (printIO) {
142 if (returnedAddress != addr) {
143 Serial.println("Wrong address returned!");
144 Serial.print("Expected ");
145 Serial.print(String(addr));
146 Serial.print(" Got ");
147 Serial.println(String(returnedAddress));
148 }
149 Serial.print("<<<");
150 Serial.write(returnedAddress);
151 Serial.print(", ");
152 Serial.println();
153 }
154
155 bool bad_read = false;
156 // While there is any data left in the buffer
157 while (mySDI12.available() && (millis() - start) < 3000) {
158 char c = mySDI12.peek();
159 // if there's a polarity sign, a number, or a decimal next in the
160 // buffer, start reading it as a float.
161 if (c == '-' || c == '+' || (c >= '0' && c <= '9') || c == '.') {
162 float result = mySDI12.parseFloat();
163 if (printIO) {
164 Serial.print("<<<");
165 Serial.println(String(result, 7));
166 } else {
167 Serial.print(String(result, 7));
168 Serial.print(", ");
169 }
170 if (result != -9999) {
171 gotResults = true;
172 cmd_results++;
173 }
174 // if we get to a new line, we've made it to the end of the response
175 } else if (c == '\r' || c == '\n') {
176 if (printIO) { Serial.write(c); }
177 mySDI12.read();
178 } else {
179 if (printIO) {
180 Serial.print(F("<<< INVALID CHARACTER IN RESPONSE:"));
181 Serial.write(c);
182 Serial.println();
183 }
184 // Read the character to make sure it's removed from the buffer
185 mySDI12.read();
186 bad_read = true;
187 }
188 delay(10); // 1 character ~ 7.5ms
189 }
190 if (!gotResults) {
191 if (printIO) {
192 Serial.println(F(" No results received, will not continue requests!"));
193 break; // don't do another loop if we got nothing
194 }
195 }
196 if (gotResults && !bad_read) {
197 resultsReceived = resultsReceived + cmd_results;
198 if (printIO) {
199 Serial.print(F(" Total Results Received: "));
200 Serial.print(resultsReceived);
201 Serial.print(F(", Remaining: "));
202 Serial.println(resultsExpected - resultsReceived);
203 }
204 cmd_number++;
205 } else {
206 // if we got a bad charater in the response, add one to the retry
207 // attempts but do not bump up the command number or transfer any
208 // results because we want to retry the same data command to try get
209 // a valid response
210 cmd_retries++;
211 }
212 mySDI12.clearBuffer();
213 }
214
215 return resultsReceived == resultsExpected;
216}
217
218bool takeMeasurement(char i, String meas_type = "") {
219 mySDI12.clearBuffer();
220 String command = "";
221 command += i;
222 command += "M";
223 command += meas_type;
224 command += "!"; // SDI-12 measurement command format [address]['M'][!]
225 mySDI12.sendCommand(command, wake_delay);
226 delay(30);
227
228 if (printIO) {
229 Serial.print(">>>");
230 Serial.println(command);
231 }
232
233 // wait for acknowledgement with format [address][ttt (3 char, seconds)][number of
234 // measurements available, 0-9]
235 String sdiResponse = mySDI12.readStringUntil('\n');
236 sdiResponse.trim();
237 if (printIO) {
238 Serial.print("<<<");
239 Serial.println(sdiResponse);
240 }
241
242 String addr = sdiResponse.substring(0, 1);
243 Serial.print(addr);
244 Serial.print(", ");
245
246 // find out how long we have to wait (in seconds).
247 uint8_t wait = sdiResponse.substring(1, 4).toInt();
248 Serial.print(wait);
249 Serial.print(", ");
250
251 // Set up the number of results to expect
252 int numResults = sdiResponse.substring(4).toInt();
253 Serial.print(numResults);
254 Serial.print(", ");
255
256 if (printIO) { Serial.println(); }
257
258 unsigned long timerStart = millis();
259 while ((millis() - timerStart) < (1000UL * (wait + 1))) {
260 if (mySDI12.available()) // sensor can interrupt us to let us know it is done early
261 {
262 if (printIO) {
263 Serial.print("<<<");
264 Serial.write(mySDI12.read());
265 Serial.print(" (");
266 }
267 Serial.print(millis() - timerStart);
268 if (printIO) {
269 Serial.println(")");
270 } else {
271 Serial.print(", ");
272 }
273 mySDI12.clearBuffer();
274 break;
275 }
276 }
277 // Wait for anything else and clear it out
278 delay(30);
279 mySDI12.clearBuffer();
280
281 if (numResults > 0) { return getResults(i, numResults); }
282
283 return true;
284}
285
286// this checks for activity at a particular address
287// expects a char, '0'-'9', 'a'-'z', or 'A'-'Z'
288bool checkActive(char i) {
289 String myCommand = "";
290 myCommand = "";
291 myCommand += (char)i; // sends basic 'acknowledge' command [address][!]
292 myCommand += "!";
293
294 for (int j = 0; j < 3; j++) { // goes through three rapid contact attempts
295 mySDI12.sendCommand(myCommand, wake_delay);
296 if (printIO) {
297 Serial.print(">>>");
298 Serial.println(myCommand);
299 }
300 delay(30);
301 if (mySDI12.available()) { // If we hear anything, assume we have an active sensor
302 if (printIO) {
303 Serial.print("<<<");
304 while (mySDI12.available()) { Serial.write(mySDI12.read()); }
305 } else {
306 mySDI12.clearBuffer();
307 }
308 return true;
309 }
310 }
311 mySDI12.clearBuffer();
312 return false;
313}
314
315void setup() {
316 Serial.begin(serialBaud);
317 while (!Serial && millis() < 10000L);
318
319 Serial.println("Opening SDI-12 bus...");
320 mySDI12.begin();
321 delay(500); // allow things to settle
322
323 Serial.println("Timeout value: ");
324 Serial.println(mySDI12.TIMEOUT);
325
326 // Power the sensors;
327 if (powerPin >= 0) {
328 Serial.println("Powering up sensors, wait...");
329 pinMode(powerPin, OUTPUT);
330 digitalWrite(powerPin, HIGH);
331 delay(5000L);
332 }
333
334 // Enable interrupts for the receive pin
335 pinMode(dataPin, INPUT_PULLUP);
336 enableInterrupt(dataPin, SDI12::handleInterrupt, CHANGE);
337
338 // Quickly scan the address space
339 Serial.println("Scanning all addresses, please wait...");
340 Serial.println("Sensor Address, Protocol Version, Sensor Vendor, Sensor Model, "
341 "Sensor Version, Sensor ID");
342
343 for (int8_t i = firstAddress; i <= lastAddress; i++) {
344 char addr = decToChar(i);
345 if (checkActive(addr)) {
346 numSensors++;
347 isActive[i] = 1;
348 printInfo(addr);
349 Serial.println();
350 }
351 }
352 Serial.print("Total number of sensors found: ");
353 Serial.println(numSensors);
354
355 if (numSensors == 0) {
356 Serial.println(
357 "No sensors found, please check connections and restart the Arduino.");
358 while (true) { delay(10); } // do nothing forever
359 }
360
361 Serial.println();
362 Serial.println("Time Elapsed (s), Sensor Address, Est Measurement "
363 "Time (s), Number Measurements, "
364 "Real Measurement Time (ms), Measurement 1, Measurement 2, ... etc.");
365 Serial.println("-------------------------------------------------------------------"
366 "------------");
367}
368
369void loop() {
370 // measure one at a time
371 for (int8_t i = firstAddress; i <= lastAddress; i++) {
372 char addr = decToChar(i);
373 if (isActive[i]) {
374 if (printIO) {
375 Serial.print(addr);
376 Serial.print(" - millis: ");
377 Serial.print(millis());
378 Serial.println();
379 } else {
380 Serial.print(millis());
381 Serial.print(", ");
382 }
383 takeMeasurement(addr);
384 Serial.println();
385 }
386 }
387
388 delay(10000L); // wait ten seconds between measurement attempts.
389}