├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md ├── examples ├── SonarI2Cdaisy │ └── SonarI2Cdaisy.ino ├── SonarI2Ctest │ └── SonarI2Ctest.ino ├── SonarI2Cv2 │ └── SonarI2Cv2.ino └── SonarI2Cx10 │ └── SonarI2Cx10.ino ├── extras └── sonarI2Cdemo1_bb.png ├── library.properties └── src ├── SonarI2C.cpp ├── SonarI2C.h └── src.ino /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | # ========================= 31 | # Operating System Files 32 | # ========================= 33 | 34 | # OSX 35 | # ========================= 36 | 37 | .DS_Store 38 | .AppleDouble 39 | .LSOverride 40 | 41 | # Thumbnails 42 | ._* 43 | 44 | # Files that might appear in the root of a volume 45 | .DocumentRevisions-V100 46 | .fseventsd 47 | .Spotlight-V100 48 | .TemporaryItems 49 | .Trashes 50 | .VolumeIcon.icns 51 | 52 | # Directories potentially created on remote AFP share 53 | .AppleDB 54 | .AppleDesktop 55 | Network Trash Folder 56 | Temporary Items 57 | .apdisk 58 | 59 | # Windows 60 | # ========================= 61 | 62 | # Windows image file caches 63 | Thumbs.db 64 | ehthumbs.db 65 | 66 | # Folder config file 67 | Desktop.ini 68 | 69 | # Recycle Bin used on file shares 70 | $RECYCLE.BIN/ 71 | 72 | # Windows Installer files 73 | *.cab 74 | *.msi 75 | *.msm 76 | *.msp 77 | 78 | # Windows shortcuts 79 | *.lnk 80 | 81 | # Arduino IDE 82 | .development 83 | 84 | # vim 85 | *~ 86 | *.swp 87 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013,2015,2017 Alastair Young 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SonarI2C library for Arduino 2 | 3 | Version: 1.1.1
4 | Release date: 2017 March 13
5 | [redhunter.com](http://redhunter.com/blog/2016/04/28/sonari2c-multiple-hc-sr04-sensors-on-arduino-i2c/) 6 | [hackaday.io](https://hackaday.io/project/19950-hc-sr04-i2c-octopus-octosonar) 7 | [tindie.com](https://www.tindie.com/products/arielnh56/octosonar-connect-8-x-hc-sr04-to-arduino/) 8 | 9 | ## Summary 10 | 11 | This is a library for the Arduino IDE that allows the polling of multiple ultrasonic distance sensors using the I2C bus and a single hardware interrupt pin. It assumes a PCF8574(A) type port expander to trigger the sensors, and external logic circuitry to multiplex the echo signals. 12 | 13 | It has been tested with the HC-SR04 sensor - the cheapest and most widely available at this time. Up to 12 units have been tested on a breadboard, using two of my Octosonar modules in a daisy-chain configuration. 14 | 15 | ## Supported Platforms 16 | 17 | This library is designed to work with the Arduino IDE versions 1.6.x or later; it is not tested it with earlier versions. It should work with any Arduino compatible board with an available hardware interrupt pin. Note that other interrupt activity may interfere with the accuracy and reliability of this code. 18 | 19 | ## Getting Started 20 | 21 | ### Hardware 22 | 23 | The minimum setup to breadboard this requires 24 | 25 | * a PCF8574 or PCF8574A IC 26 | * two HC-SR04 sensors 27 | * two NPN transistors and some resistors to make a NOR gate 28 | * example sketch SonarI2Cv2 29 | 30 | ![basic breadboard test](https://github.com/arielnh56/SonarI2C/blob/master/extras/sonarI2Cdemo1_bb.png) 31 | 32 | For pracitcal use you can replicate my prototype design at [redhunter.com](http://redhunter.com/blog/2016/04/28/sonari2c-multiple-hc-sr04-sensors-on-arduino-i2c/) or purchase my Octosonar boards at [tindie.com](https://www.tindie.com/products/arielnh56/octosonar-connect-8-x-hc-sr04-to-arduino/) 33 | 34 | ### Software 35 | 36 | If you are using version 1.6.2 or later of the [Arduino software (IDE)](http://www.arduino.cc/en/Main/Software), you can use the Library Manager to install this library: 37 | 38 | 1. In the Arduino IDE, open the "Sketch" menu, select "Include Library", then "Manage Libraries...". 39 | 2. Search for "SonarI2C". 40 | 3. Click the SonarI2C entry in the list. 41 | 4. Click "Install". 42 | 43 | If this does not work, you can manually install the library: 44 | 45 | 1. Download the [latest release archive from GitHub](https://github.com/arielnh56/SonarI2C/releases) and decompress it. 46 | 2. Rename the folder "SonarI2C-master" to "SonarI2C". 47 | 3. Move the "SonarI2C" folder into the "libraries" directory inside your Arduino sketchbook directory. You can view your sketchbook location by opening the "File" menu and selecting "Preferences" in the Arduino IDE. If there is not already a "libraries" folder in that location, you should make the folder yourself. 48 | 4. After installing the library, restart the Arduino IDE. 49 | 50 | (note - the above instructions adapted from a Pololu readme) 51 | 52 | ## Examples 53 | 54 | Two examples are included showing the use of two and ten sensors. Aother example will display 8 outputs on an LCD. 55 | 56 | ## Library reference 57 | 58 | * Constructor. Call once for each sensor outside loop(0 and setup(). This sets the I2C address of the pin expander (0x20 - 0x27, 0x38 - 0x3F), the pin on the expander (0-7) and the maximum range in mm (0-4000). 59 | 60 | ```c 61 | SonarI2C(uint8_t address, uint8_t pin, uint16_t max_mm) 62 | ``` 63 | 64 | * call one of the begin() functions from setup() before you call init() on the individual SonarI2C members. This sets the interrupt pin to use and the spacing between sonar pings. The default is 50ms to ensure no interference from old echoes. Reduce this for faster but less reliable operation. 65 | 66 | ```c 67 | static void begin(); // call from setup(). Defaults to pin 2 and 50ms 68 | static void begin(uint8_t interrupt); // same thing but set the pin 69 | static void begin(uint8_t interrupt, uint16_t spacing); // same thing but set the pin and spacing 70 | ``` 71 | 72 | * by default the interrupt pin is looking for a positive echo pulse. If you are using NOR logic (as in the breadboard example) set 'inverse' to 'true' 73 | 74 | ```c 75 | static boolean inverse; 76 | ``` 77 | 78 | * by default we skip one out of range/failed echo,keeping the last value. It may be useful to raise this count in sub-optimal echo environments. 79 | 80 | ```c 81 | static uint8_t maxOOR; // how many OOR to skip. Raise this in noisy environments 82 | ``` 83 | 84 | * call init() on each SonarI2C from setup(). It initilizes some values and places it in the polling queue 85 | 86 | ```c 87 | void init(); 88 | ``` 89 | 90 | * Disabling sensors that you are not interested in right now allows the other sensors to poll more often. 91 | 92 | ```c 93 | boolean enabled(); // query if enabled 94 | void enable(boolean enabled); // set _enable 95 | ``` 96 | 97 | * Call this every loop(). If it is time, it will poll the next enabled sensor. 98 | 99 | ```c 100 | static void doSonar(); 101 | ``` 102 | 103 | * Return values 104 | ```c 105 | int16_t mm(); // return distance in mm. 106 | int16_t cm(); // return distance in cm 107 | int16_t inch(); // return distance in inches 108 | int16_t us(); // return echo time in microseconds 109 | ``` 110 | currently uses floats, which is inefficient 111 | 112 | ## Version history 113 | 114 | * 1.0.0 (2016 April 28): Original release. 115 | * 1.1.0 (2017 March 13): Improved OOR handling, MIT license. 116 | * 1.1.1 (2017 March 13): Update README 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /examples/SonarI2Cdaisy/SonarI2Cdaisy.ino: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* 4 | * Test script for SonaI2C 5 | * 6 | * This uses the LiquidCrystal_PCF8574 lubrary to drive an 2004 LCD display 7 | * mm results are displayed anti-clockwise around the display to match the OctoSonar board layout 8 | * Values are updated every 500ms 9 | * 10 | */ 11 | // #define I2C_MASTER_ADDRESS 9 // for author's dual brain test bot 12 | #define I2C_LCD_ADDRESS 0x27 // default for the current flavor of cheap I2C backpack 13 | #define I2C_SONAR_ADDRESS 0x20 // change to 0x3B for default OctoSonar 14 | #include "SonarI2C.h" 15 | // #include // old NewLiquidCrystal library 16 | #include 17 | 18 | // LiquidCrystal_I2C lcd(I2C_LCD_ADDRESS, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // // old NewLiquidCrystal library 19 | LiquidCrystal_PCF8574 lcd(I2C_LCD_ADDRESS); 20 | 21 | uint8_t numsonars = 8; 22 | 23 | // Numeric Sequence 24 | SonarI2C sonars[] = { 25 | SonarI2C (I2C_SONAR_ADDRESS, 0, 4000), 26 | SonarI2C (I2C_SONAR_ADDRESS, 1, 4000), 27 | SonarI2C (I2C_SONAR_ADDRESS, 2, 4000), 28 | SonarI2C (I2C_SONAR_ADDRESS, 3, 4000), 29 | SonarI2C (I2C_SONAR_ADDRESS, 4, 4000), 30 | SonarI2C (I2C_SONAR_ADDRESS, 5, 4000), 31 | SonarI2C (I2C_SONAR_ADDRESS, 6, 4000), 32 | SonarI2C (I2C_SONAR_ADDRESS, 7, 4000), 33 | }; 34 | 35 | 36 | 37 | 38 | void setup() { 39 | lcd.begin(20, 4); // initialize the lcd 40 | lcd.setBacklight(255); // turn on the backlight 41 | lcd.clear(); // go home 42 | Serial.begin(115200); 43 | SonarI2C::begin(); // initialize bus, pins etc 44 | // SonarI2C::begin(2); // alternate form, set interrupt pin, 2 is the default 45 | // SonarI2C::begin(2, 50); // alternate form also sets spacing, 50ms is the default 46 | SonarI2C::inverse = false; 47 | for (uint8_t i = 0; i < numsonars; i++) { 48 | sonars[i].init(); 49 | } 50 | } 51 | 52 | uint32_t last_print = 0; 53 | void loop() { 54 | char buffer[20]; 55 | SonarI2C::doSonar(); // call every cycle, SonarI2C handles the spacing 56 | 57 | if (last_print + 500 < millis()) { 58 | last_print = millis(); 59 | for (uint8_t i = 0; i < numsonars; i++) { 60 | // Serial.print(sonars[i].mm()); Serial.print(" "); 61 | } 62 | // Serial.println(); 63 | sprintf(buffer, "0 %5d %5d 7 ", sonars[0].mm(), sonars[7].mm()); 64 | lcd.setCursor(0, 0); lcd.print(buffer); 65 | sprintf(buffer, "1 %5d %5d 6", sonars[1].mm(), sonars[6].mm()); 66 | lcd.setCursor(0, 1); lcd.print(buffer); 67 | sprintf(buffer, "2 %5d %5d 5", sonars[2].mm(), sonars[5].mm()); 68 | lcd.setCursor(0, 2); lcd.print(buffer); 69 | sprintf(buffer, "3 %5d %5d 4", sonars[3].mm(), sonars[4].mm()); 70 | lcd.setCursor(0, 3); lcd.print(buffer); 71 | } 72 | } 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /examples/SonarI2Ctest/SonarI2Ctest.ino: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* 4 | * Test script for SonaI2C 5 | * 6 | * This uses the LiquidCrystal_PCF8574 lubrary to drive an 2004 LCD display 7 | * mm results are displayed anti-clockwise around the display to match the OctoSonar board layout 8 | * Values are updated every 500ms 9 | * 10 | */ 11 | // #define I2C_MASTER_ADDRESS 9 // for author's dual brain test bot 12 | #define I2C_LCD_ADDRESS 0x27 // default for the current flavor of cheap I2C backpack 13 | #define I2C_SONAR_ADDRESS 0x20 // change to 0x3B for default OctoSonar 14 | #include "SonarI2C.h" 15 | // #include // old NewLiquidCrystal library 16 | #include 17 | 18 | // LiquidCrystal_I2C lcd(I2C_LCD_ADDRESS, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // // old NewLiquidCrystal library 19 | LiquidCrystal_PCF8574 lcd(I2C_LCD_ADDRESS); 20 | 21 | uint8_t numsonars = 8; 22 | 23 | // Numeric Sequence 24 | SonarI2C sonars[] = { 25 | SonarI2C (I2C_SONAR_ADDRESS, 0, 4000), 26 | SonarI2C (I2C_SONAR_ADDRESS, 1, 4000), 27 | SonarI2C (I2C_SONAR_ADDRESS, 2, 4000), 28 | SonarI2C (I2C_SONAR_ADDRESS, 3, 4000), 29 | SonarI2C (I2C_SONAR_ADDRESS, 4, 4000), 30 | SonarI2C (I2C_SONAR_ADDRESS, 5, 4000), 31 | SonarI2C (I2C_SONAR_ADDRESS, 6, 4000), 32 | SonarI2C (I2C_SONAR_ADDRESS, 7, 4000), 33 | }; 34 | 35 | 36 | 37 | 38 | void setup() { 39 | lcd.begin(20, 4); // initialize the lcd 40 | lcd.setBacklight(255); // turn on the backlight 41 | lcd.clear(); // go home 42 | Serial.begin(115200); 43 | SonarI2C::begin(); // initialize bus, pins etc 44 | // SonarI2C::begin(2); // alternate form, set interrupt pin, 2 is the default 45 | // SonarI2C::begin(2, 200); // alternate form also sets spacing, 50ms is the default 46 | SonarI2C::inverse = false; 47 | // SonarI2C::maxOOR = 5; 48 | for (uint8_t i = 0; i < numsonars; i++) { 49 | sonars[i].init(); 50 | } 51 | } 52 | 53 | uint32_t last_print = 0; 54 | void loop() { 55 | char buffer[20]; 56 | SonarI2C::doSonar(); // call every cycle, SonarI2C handles the spacing 57 | 58 | if (last_print + 500 < millis()) { 59 | last_print = millis(); 60 | for (uint8_t i = 0; i < numsonars; i++) { 61 | // Serial.print(sonars[i].mm()); Serial.print(" "); 62 | } 63 | // Serial.println(); 64 | sprintf(buffer, "0 %5d %5d 7 ", sonars[0].mm(), sonars[7].mm()); 65 | lcd.setCursor(0, 0); lcd.print(buffer); 66 | sprintf(buffer, "1 %5d %5d 6", sonars[1].mm(), sonars[6].mm()); 67 | lcd.setCursor(0, 1); lcd.print(buffer); 68 | sprintf(buffer, "2 %5d %5d 5", sonars[2].mm(), sonars[5].mm()); 69 | lcd.setCursor(0, 2); lcd.print(buffer); 70 | sprintf(buffer, "3 %5d %5d 4", sonars[3].mm(), sonars[4].mm()); 71 | lcd.setCursor(0, 3); lcd.print(buffer); 72 | } 73 | } 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /examples/SonarI2Cv2/SonarI2Cv2.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * This demo goes with the fritzing breadboard example 3 | * two sensors on pins 4 and 5 with the outputs multiplexed back to pin 2 4 | * via transistor NOR gate 5 | */ 6 | 7 | 8 | #include "SonarI2C.h" 9 | 10 | SonarI2C frontSonar(0x20, 4, 4000); 11 | SonarI2C backSonar(0x20, 5, 4000); 12 | 13 | void setup() { 14 | Serial.begin(115200); 15 | SonarI2C::begin(); // initialize bus, pins etc 16 | SonarI2C::inverse = true; // NOR logic 17 | // SonarI2C::begin(2); // alternate form, set interrupt pin, 2 is the default 18 | // SonarI2C::begin(2, 50); // alternate form also sets spacing, 50ms is the default 19 | 20 | frontSonar.init(); // clear expander and add to list 21 | backSonar.init(); // clear expander and add to list 22 | } 23 | 24 | uint32_t last_print = 0; 25 | void loop() { 26 | SonarI2C::doSonar(); // call every cycle, SonarI2C handles the spacing 27 | 28 | if (last_print + 100 < millis()) { 29 | last_print=millis(); 30 | Serial.print(frontSonar.mm()); Serial.print(" "); 31 | Serial.println(backSonar.mm()); 32 | } 33 | } 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/SonarI2Cx10/SonarI2Cx10.ino: -------------------------------------------------------------------------------- 1 | 2 | #include "SonarI2C.h" 3 | 4 | uint8_t numsonars = 10; 5 | SonarI2C sonars[] = { 6 | SonarI2C (0x20, 0, 4000), 7 | SonarI2C (0x20, 1, 4000), 8 | SonarI2C (0x20, 2, 4000), 9 | SonarI2C (0x20, 3, 4000), 10 | SonarI2C (0x20, 4, 4000), 11 | SonarI2C (0x20, 5, 4000), 12 | SonarI2C (0x20, 6, 4000), 13 | SonarI2C (0x20, 7, 4000), 14 | SonarI2C (0x21, 0, 4000), 15 | SonarI2C (0x21, 1, 4000), 16 | }; 17 | 18 | void setup() { 19 | Serial.begin(115200); 20 | SonarI2C::begin(); // initialize bus, pins etc 21 | // SonarI2C::begin(2); // alternate form, set interrupt pin, 2 is the default 22 | // SonarI2C::begin(2, 50); // alternate form also sets spacing, 50ms is the default 23 | SonarI2C::inverse = false; 24 | for (uint8_t i = 0; i < numsonars; i++) { 25 | sonars[i].init(); 26 | } 27 | } 28 | 29 | uint32_t last_print = 0; 30 | void loop() { 31 | SonarI2C::doSonar(); // call every cycle, SonarI2C handles the spacing 32 | 33 | if (last_print + 100 < millis()) { 34 | last_print = millis(); 35 | for (uint8_t i = 0; i < numsonars; i++) { 36 | Serial.print(sonars[i].mm()); Serial.print(" "); 37 | } 38 | Serial.println(); 39 | } 40 | } 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /extras/sonarI2Cdemo1_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arielnh56/SonarI2C/e67d3c7e3374a9906cc4e1ebb8af111ceececd03/extras/sonarI2Cdemo1_bb.png -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=SonarI2C 2 | version=1.1.2 3 | author=Alastair Young 4 | maintainer=Alastair Young 5 | sentence=A library to support cheap ultrasonic sensors on I2C bus 6 | paragraph=triggers via PCF8574, echo via OR/NOR logic to hardware interrupt. Tested with up to 10 HC-SR04 7 | category=Sensors 8 | url=http://redhunter.com/ 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/SonarI2C.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SonarI2C.cpp 3 | Copyright (c) 2016,2017 Alastair Young. 4 | This project is licensed under the terms of the MIT license. 5 | */ 6 | #include "SonarI2C.h" 7 | #include 8 | 9 | // constructor 10 | SonarI2C::SonarI2C(uint8_t address, uint8_t pin, uint16_t max_mm) { 11 | _address = (address < 0x38) ? constrain(address, 0x20, 0x27) : constrain(address, 0x38, 0x3F); 12 | _pin = constrain(pin, 0, 7); 13 | _max_micros = constrain(max_mm, 20, 4000) / 0.170145; // constrain to spec sheet 14 | _enabled = true; 15 | _OORcount = 0; 16 | } 17 | 18 | // member initializer - call from setup() 19 | void SonarI2C::init() { 20 | Wire.beginTransmission(_address); 21 | Wire.write(0); // clear the expander 22 | Wire.endTransmission(); 23 | _micros = 0; 24 | // insert into circular linked list 25 | if (_currentSonar) { 26 | _nextSonar = _currentSonar->_nextSonar; 27 | _currentSonar->_nextSonar = this; 28 | } else { // new list 29 | _currentSonar = this; 30 | _nextSonar = this; 31 | } 32 | } 33 | 34 | // private member functions 35 | // send the trigger signal and attach the interrupt 36 | void SonarI2C::_send_ping() { 37 | _pulseBegin = 0; // cleared until we see it 38 | attachInterrupt(digitalPinToInterrupt(_interrupt), _startPulse, inverse ? FALLING : RISING); // NOR gate - pulse is inverted 39 | Wire.beginTransmission(_address); 40 | Wire.write(1 << _pin); // set the pin 41 | Wire.endTransmission(); 42 | // at 100kHZ this seems to be about 240us pulse. More than the spec but works fine. 43 | Wire.beginTransmission(_address); 44 | Wire.write(0); // clear all pins 45 | Wire.endTransmission(); 46 | } 47 | 48 | void SonarI2C::_startPulse() { 49 | _pulseBegin = micros(); // pulse is starting now 50 | attachInterrupt(digitalPinToInterrupt(_interrupt), _endPulse, inverse ? RISING : FALLING); // now look for pulse end 51 | } 52 | 53 | void SonarI2C::_endPulse() { 54 | uint32_t now = micros(); 55 | detachInterrupt(digitalPinToInterrupt(_interrupt)); // clean up after ourselves 56 | // ignore wacko values 57 | if (now < _pulseBegin) return; // we started in the future - micros rollover or the like 58 | uint16_t pulseLen = now - _pulseBegin; // calculate length of pulse 59 | if (pulseLen > _currentSonar->_max_micros) { // took too long 0 = out of range 60 | _currentSonar->_OORcount++; 61 | if (_currentSonar->_OORcount > maxOOR) { 62 | _currentSonar->_micros = 0; 63 | } else { 64 | return; //skip this time 65 | } 66 | } else { 67 | _currentSonar->_OORcount = 0; 68 | _currentSonar->_micros = pulseLen; 69 | } 70 | } 71 | 72 | // public member functions 73 | int16_t SonarI2C::us() { 74 | return _micros; 75 | } 76 | 77 | int16_t SonarI2C::mm() { 78 | return _micros * 0.170145; 79 | } 80 | 81 | int16_t SonarI2C::cm() { 82 | return _micros * 0.0170145; 83 | } 84 | 85 | int16_t SonarI2C::inch() { 86 | return _micros * 0.00669862; 87 | } 88 | 89 | void SonarI2C::enable(boolean enabled) { 90 | _enabled = enabled; 91 | if (!enabled) _micros = 0; 92 | } 93 | 94 | // declare private static class variables 95 | SonarI2C *SonarI2C::_currentSonar = NULL; 96 | uint32_t SonarI2C::_last_sonar_millis = 0; 97 | uint32_t SonarI2C::_pulseBegin = 0; 98 | uint8_t SonarI2C::_interrupt = SONAR_INT_PIN; 99 | uint16_t SonarI2C::_spacing = SONAR_SPACING; 100 | boolean SonarI2C::inverse = false; 101 | uint8_t SonarI2C::maxOOR = 1; 102 | 103 | // public static class functions 104 | // call a begin() from setup() 105 | 106 | void SonarI2C::begin(uint8_t interrupt, uint16_t spacing) { 107 | _interrupt = interrupt; 108 | _spacing = spacing; 109 | begin(); 110 | } 111 | 112 | void SonarI2C::begin() { 113 | Wire.begin(); 114 | pinMode(_interrupt, INPUT); 115 | } 116 | 117 | void SonarI2C::begin(uint8_t interrupt) { 118 | _interrupt = interrupt; 119 | begin(); 120 | } 121 | 122 | // call from loop() every cycle 123 | void SonarI2C::doSonar() { 124 | if (!_currentSonar) return; // no sonars initiated 125 | if (digitalRead(_interrupt) == inverse ? LOW : HIGH) return; // interrupt pin is active - skip 126 | if (_last_sonar_millis + _spacing < millis()) { 127 | // look for next enabled sonar 128 | SonarI2C *thisSonar = _currentSonar->_nextSonar; 129 | while (!thisSonar->_enabled) { 130 | if (thisSonar == _currentSonar) return; // no enables found - skip 131 | thisSonar = thisSonar->_nextSonar; 132 | } 133 | 134 | _last_sonar_millis = millis(); 135 | _currentSonar = thisSonar; 136 | _currentSonar->_send_ping(); 137 | } 138 | } 139 | 140 | -------------------------------------------------------------------------------- /src/SonarI2C.h: -------------------------------------------------------------------------------- 1 | /* 2 | SonarI2C.cpp 3 | Copyright (c) 2016,2017 Alastair Young. 4 | This project is licensed under the terms of the MIT license. 5 | */ 6 | #ifndef SONARI2C_H 7 | #define SONARI2C_H 8 | 9 | #if defined(ARDUINO) && ARDUINO >= 100 10 | #include "Arduino.h" 11 | #else 12 | #include "WProgram.h" 13 | #endif 14 | 15 | #define SONAR_INT_PIN 2 16 | #define SONAR_SPACING 50 // millis to avoid echo 17 | 18 | class SonarI2C { 19 | public: 20 | 21 | SonarI2C(uint8_t address, uint8_t pin, uint16_t max_mm); // constructor 22 | 23 | void init(); // member initializer. Call for each member in setup() 24 | int16_t mm(); // return distance in mm 25 | int16_t cm(); // return distance in cm 26 | int16_t inch(); // return distance in inches 27 | int16_t us(); // return echo time in microseconds 28 | boolean enabled(); // query if enabled 29 | void enable(boolean enabled); // set _enable 30 | static boolean inverse; // is logic inverted from sensor? 31 | static void begin(); // call from setup(). Defaults to pin 2 and 50ms 32 | static void begin(uint8_t interrupt); // same thing but set the pin 33 | static void begin(uint8_t interrupt, uint16_t spacing); // same thing but set the pin and spacing 34 | static void doSonar(); // call every loop() 35 | static uint8_t maxOOR; // how many OOR to skip. Raise this in noisy environments 36 | 37 | private: 38 | uint8_t _address; // PCF8574 0x20 - 0x27 or PCF8574A 0x38 - 0x3F 39 | uint8_t _pin; // expander pin 0 -7 40 | uint16_t _max_micros; // limit in microseconds. Above this return zero. 41 | uint16_t _micros; // last good response time in microseconds 42 | SonarI2C *_nextSonar; // link to the next SonarI2C in the list 43 | boolean _enabled; // skip if enabled = FALSE 44 | void _send_ping(); // trigger the sensor and set the interrupt 45 | uint8_t _OORcount; // count how many out of range pings in a row 46 | static uint32_t _pulseBegin; // to remember when the pulse started 47 | static uint16_t _spacing; // time between pings - default 50ms 48 | static uint8_t _interrupt; // interrupt pin - default 2 49 | static uint32_t _last_sonar_millis; // timestamp of last ping sent 50 | static void _startPulse(); // interrupt callout when a pulse starts 51 | static void _endPulse(); // interrupt callout when a pulse ends 52 | static SonarI2C *_currentSonar; // pointer to the active sensor 53 | }; 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/src.ino: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arielnh56/SonarI2C/e67d3c7e3374a9906cc4e1ebb8af111ceececd03/src/src.ino --------------------------------------------------------------------------------