├── .github ├── FUNDING.yml └── workflows │ ├── arduino-lint.yml │ ├── jsoncheck.yml │ └── arduino_test_runner.yml ├── library.properties ├── library.json ├── .arduino-ci.yml ├── keywords.txt ├── examples ├── I2C_scanner_minimal │ └── I2C_scanner_minimal.ino ├── I2C_scanner_simple │ └── I2C_scanner_simple.ino ├── I2C_scanner_count │ └── I2C_scanner_count.ino ├── I2C_scanner_multi_speed │ └── I2C_scanner_multi_speed.ino ├── I2C_scanner_getWirePortCount │ └── I2C_scanner_getWirePortCount.ino └── I2C_scanner_ping_count │ └── I2C_scanner_ping_count.ino ├── LICENSE ├── CHANGELOG.md ├── test └── unit_test_001.cpp ├── I2C_SCANNER.h ├── I2C_SCANNER.cpp └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: RobTillaart 4 | custom: "https://www.paypal.me/robtillaart" 5 | -------------------------------------------------------------------------------- /.github/workflows/arduino-lint.yml: -------------------------------------------------------------------------------- 1 | name: Arduino-lint 2 | 3 | on: [push, pull_request] 4 | jobs: 5 | lint: 6 | runs-on: ubuntu-latest 7 | timeout-minutes: 5 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: arduino/arduino-lint-action@v1 11 | with: 12 | library-manager: update 13 | compliance: strict -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=I2C_SCANNER 2 | version=0.3.1 3 | author=Rob Tillaart 4 | maintainer=Rob Tillaart 5 | sentence=Arduino class to implement an I2C scanner. 6 | paragraph=. 7 | category=Other 8 | url=https://github.com/RobTillaart/I2C_SCANNER 9 | architectures=* 10 | includes=I2C_SCANNER.h 11 | depends= 12 | -------------------------------------------------------------------------------- /.github/workflows/jsoncheck.yml: -------------------------------------------------------------------------------- 1 | name: JSON check 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.json' 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 5 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: json-syntax-check 16 | uses: limitusus/json-syntax-check@v2 17 | with: 18 | pattern: "\\.json$" -------------------------------------------------------------------------------- /.github/workflows/arduino_test_runner.yml: -------------------------------------------------------------------------------- 1 | name: Arduino CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | runTest: 7 | runs-on: ubuntu-latest 8 | timeout-minutes: 20 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: ruby/setup-ruby@v1 13 | with: 14 | ruby-version: 2.6 15 | - run: | 16 | gem install arduino_ci 17 | arduino_ci.rb 18 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "I2C_SCANNER", 3 | "keywords": "I2C,scanner", 4 | "description": "Arduino class to implement an I2C scanner.", 5 | "authors": 6 | [ 7 | { 8 | "name": "Rob Tillaart", 9 | "email": "Rob.Tillaart@gmail.com", 10 | "maintainer": true 11 | } 12 | ], 13 | "repository": 14 | { 15 | "type": "git", 16 | "url": "https://github.com/RobTillaart/I2C_SCANNER.git" 17 | }, 18 | "version": "0.3.1", 19 | "license": "MIT", 20 | "frameworks": "*", 21 | "platforms": "*", 22 | "headers": "I2C_SCANNER.h" 23 | } 24 | -------------------------------------------------------------------------------- /.arduino-ci.yml: -------------------------------------------------------------------------------- 1 | platforms: 2 | rpipico: 3 | board: rp2040:rp2040:rpipico 4 | package: rp2040:rp2040 5 | gcc: 6 | features: 7 | defines: 8 | - ARDUINO_ARCH_RP2040 9 | warnings: 10 | flags: 11 | 12 | packages: 13 | rp2040:rp2040: 14 | url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json 15 | 16 | compile: 17 | # Choosing to run compilation tests on 2 different Arduino platforms 18 | platforms: 19 | - uno 20 | - due 21 | - zero 22 | - leonardo 23 | - m4 24 | - esp32 25 | - esp8266 26 | - mega2560 27 | - rpipico 28 | 29 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | # Syntax Colouring Map For I2C_SCANNER 2 | 3 | # Data types (KEYWORD1) 4 | I2C_SCANNER KEYWORD1 5 | 6 | # Methods and Functions (KEYWORD2) 7 | begin KEYWORD2 8 | 9 | getWirePortCount KEYWORD2 10 | setWire KEYWORD2 11 | getWire KEYWORD2 12 | 13 | softwareReset KEYWORD2 14 | 15 | setClock KEYWORD2 16 | getClock KEYWORD2 17 | 18 | ping KEYWORD2 19 | diag KEYWORD2 20 | pingTime KEYWORD2 21 | count KEYWORD2 22 | 23 | setWireTimeout KEYWORD2 24 | getWireTimeout KEYWORD2 25 | 26 | #if supported they get highlighted 27 | getSDA KEYWORD2 28 | getSCL KEYWORD2 29 | 30 | getDeviceID KEYWORD2 31 | 32 | 33 | # Constants (LITERAL1) 34 | I2C_SCANNER_LIB_VERSION LITERAL1 35 | 36 | -------------------------------------------------------------------------------- /examples/I2C_scanner_minimal/I2C_scanner_minimal.ino: -------------------------------------------------------------------------------- 1 | // FILE: I2C_scanner_minimal.ino 2 | // AUTHOR: Rob Tillaart 3 | // PURPOSE: demo minimal I2C scanner 4 | // URL: https://github.com/RobTillaart/I2C_SCANNER 5 | 6 | 7 | #include "I2C_SCANNER.h" 8 | 9 | I2C_SCANNER scanner; 10 | 11 | 12 | void setup() 13 | { 14 | Serial.begin(115200); 15 | while (!Serial); 16 | Serial.println(); 17 | Serial.println(__FILE__); 18 | Serial.print("I2C_SCANNER_LIB_VERSION: "); 19 | Serial.println(I2C_SCANNER_LIB_VERSION); 20 | 21 | Wire.begin(); 22 | scanner.begin(); 23 | 24 | for (int addr = 0; addr < 128; addr++) 25 | { 26 | Serial.print(addr, HEX); 27 | Serial.print("\t"); 28 | Serial.print(addr); 29 | Serial.print("\t"); 30 | Serial.println(scanner.ping(addr)); 31 | } 32 | } 33 | 34 | 35 | void loop() 36 | { 37 | } 38 | 39 | 40 | // -- END OF FILE -- 41 | 42 | -------------------------------------------------------------------------------- /examples/I2C_scanner_simple/I2C_scanner_simple.ino: -------------------------------------------------------------------------------- 1 | // FILE: I2C_scanner_simple.ino 2 | // AUTHOR: Rob Tillaart 3 | // PURPOSE: demo simple scanner 4 | // URL: https://github.com/RobTillaart/I2C_SCANNER 5 | 6 | 7 | #include "I2C_SCANNER.h" 8 | 9 | I2C_SCANNER scanner; 10 | 11 | 12 | void setup() 13 | { 14 | Serial.begin(115200); 15 | while (!Serial); 16 | Serial.println(); 17 | Serial.println(__FILE__); 18 | Serial.print("I2C_SCANNER_LIB_VERSION: "); 19 | Serial.println(I2C_SCANNER_LIB_VERSION); 20 | 21 | Wire.begin(); 22 | scanner.begin(); 23 | } 24 | 25 | 26 | void loop() 27 | { 28 | for (int addr = 0; addr < 128; addr++) 29 | { 30 | if (addr % 8 == 0) Serial.println(); 31 | if (scanner.ping(addr)) 32 | { 33 | Serial.print(addr); 34 | } 35 | else 36 | { 37 | Serial.print("-"); 38 | } 39 | Serial.print("\t"); 40 | } 41 | Serial.println(); 42 | Serial.println(); 43 | delay(1000); 44 | } 45 | 46 | 47 | // -- END OF FILE -- 48 | 49 | -------------------------------------------------------------------------------- /examples/I2C_scanner_count/I2C_scanner_count.ino: -------------------------------------------------------------------------------- 1 | // FILE: I2C_scanner_count.ino 2 | // AUTHOR: Rob Tillaart 3 | // PURPOSE: demo simple scanner 4 | // URL: https://github.com/RobTillaart/I2C_SCANNER 5 | 6 | 7 | #include "I2C_SCANNER.h" 8 | 9 | I2C_SCANNER scanner; 10 | 11 | 12 | void setup() 13 | { 14 | Serial.begin(115200); 15 | while (!Serial); 16 | Serial.println(); 17 | Serial.println(__FILE__); 18 | Serial.print("I2C_SCANNER_LIB_VERSION: "); 19 | Serial.println(I2C_SCANNER_LIB_VERSION); 20 | 21 | Wire.begin(); 22 | scanner.begin(); 23 | 24 | int ports = scanner.getWirePortCount(); 25 | Serial.print("PORTS: \t"); 26 | Serial.println(ports); 27 | 28 | for (int port = 0; port < ports; port++) 29 | { 30 | scanner.setWire(port); 31 | int cnt = scanner.count(); 32 | Serial.print("Port: \t"); 33 | Serial.print(port); 34 | Serial.print("\tCount: \t"); 35 | Serial.println(cnt); 36 | } 37 | 38 | Serial.println("\ndone..."); 39 | } 40 | 41 | 42 | void loop() 43 | { 44 | } 45 | 46 | 47 | // -- END OF FILE -- 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2025 Rob Tillaart 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 | -------------------------------------------------------------------------------- /examples/I2C_scanner_multi_speed/I2C_scanner_multi_speed.ino: -------------------------------------------------------------------------------- 1 | // FILE: I2C_scanner_multi_speed.ino 2 | // AUTHOR: Rob Tillaart 3 | // PURPOSE: demo minimal I2C scanner 4 | // URL: https://github.com/RobTillaart/I2C_SCANNER 5 | 6 | 7 | #include "I2C_SCANNER.h" 8 | 9 | I2C_SCANNER scanner; 10 | 11 | 12 | void setup() 13 | { 14 | Serial.begin(115200); 15 | while (!Serial); 16 | Serial.println(); 17 | Serial.println(__FILE__); 18 | Serial.print("I2C_SCANNER_LIB_VERSION: "); 19 | Serial.println(I2C_SCANNER_LIB_VERSION); 20 | Serial.println(); 21 | 22 | Wire.begin(); 23 | 24 | scanner.begin(); 25 | scanner.setClock(100000); 26 | scanner.setWireTimeout(25000, false); 27 | 28 | for (int addr = 0; addr < 128; addr++) 29 | { 30 | scanner.setClock(100000); 31 | int x = scanner.ping(addr); 32 | Serial.print(addr, HEX); 33 | Serial.print("\t"); 34 | Serial.print(addr); 35 | Serial.print("\t"); 36 | Serial.println(x); 37 | if (x >= 1) 38 | { 39 | for (uint32_t clock = 100000; clock <= 600000; clock += 50000) 40 | { 41 | scanner.setClock(clock); 42 | Serial.print("\t"); 43 | Serial.print(clock); 44 | Serial.print("\t"); 45 | Serial.println(scanner.ping(addr, 4)); // how much success of 4 pings? 46 | } 47 | } 48 | } 49 | scanner.setClock(100000); 50 | } 51 | 52 | 53 | void loop() 54 | { 55 | } 56 | 57 | 58 | // -- END OF FILE -- 59 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log I2C_SCANNER 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | 9 | ## [0.3.1] - 2025-03-12 10 | - update examples 11 | - implement experimental **setWireTimeout()** 12 | - add experimental **uint32_t getDeviceID(uint8_t address)** 13 | - add experimental getSDA() + getSCL() 14 | - update readme.md 15 | - minor edits 16 | 17 | ## [0.3.0] - 2023-12-05 18 | - remove **begin(sda, scl)** to have less board specific dependencies. 19 | 20 | ---- 21 | 22 | ## [0.2.1] - 2023-09-24 23 | - fix Wire1 support ESP32 24 | - update keywords.txt 25 | 26 | ## [0.2.0] - 2023-08-21 27 | - changed interface for **uint16_t ping(address, uint16_t count = 1)** 28 | - allows multiple tries, for extended diagnosis. 29 | - add example 30 | - update readme.md 31 | - add issue-count badge 32 | - add PlatformIO badge 33 | - minor edits 34 | 35 | ---- 36 | 37 | ## [0.1.4] - 2022-10-30 38 | - update GitHub actions 39 | - update license 2023 40 | - add experimental **setWireTimeout()** 41 | - minor edit examples 42 | 43 | ## [0.1.3] - 2022-10-30 44 | - Add RP2040 support to build-CI. 45 | - Add CHANGELOG.md 46 | - extend example 47 | 48 | ## [0.1.2] - 2022-08-30 49 | - elaborate up to useful version 50 | 51 | ## [0.1.1] - 2022-08-29 52 | - elaborate more 53 | 54 | ## [0.1.0] - 2022-08-29 55 | - initial version 56 | -------------------------------------------------------------------------------- /examples/I2C_scanner_getWirePortCount/I2C_scanner_getWirePortCount.ino: -------------------------------------------------------------------------------- 1 | // FILE: I2C_scanner_getWirePortCount.ino 2 | // AUTHOR: Rob Tillaart 3 | // PURPOSE: demo simple scanner 4 | // URL: https://github.com/RobTillaart/I2C_SCANNER 5 | 6 | 7 | #include "I2C_SCANNER.h" 8 | 9 | I2C_SCANNER scanner; 10 | 11 | 12 | void setup() 13 | { 14 | Serial.begin(115200); 15 | while (!Serial); 16 | Serial.println(); 17 | Serial.println(__FILE__); 18 | Serial.print("I2C_SCANNER_LIB_VERSION: "); 19 | Serial.println(I2C_SCANNER_LIB_VERSION); 20 | 21 | Wire.begin(); 22 | scanner.begin(); 23 | 24 | Serial.print("I2C ports: \t"); 25 | Serial.println(scanner.getWirePortCount()); 26 | 27 | for (int port = 0; port < scanner.getWirePortCount(); port++) 28 | { 29 | if (scanner.setWire(port)) 30 | { 31 | Serial.println(); 32 | Serial.print("Scan port: \t"); 33 | Serial.println(port); 34 | 35 | int fnd = simpleScan(); 36 | Serial.print("found: \t"); 37 | Serial.println(fnd); 38 | } 39 | } 40 | } 41 | 42 | 43 | void loop() 44 | { 45 | } 46 | 47 | 48 | int simpleScan() 49 | { 50 | int count = 0; 51 | for (int addr = 0; addr < 128; addr++) 52 | { 53 | if (addr % 8 == 0) Serial.println(); 54 | if (scanner.ping(addr)) 55 | { 56 | Serial.print(addr); 57 | count++; 58 | } 59 | else 60 | { 61 | Serial.print("-"); 62 | } 63 | Serial.print("\t"); 64 | } 65 | Serial.println(); 66 | Serial.println(); 67 | return count; 68 | } 69 | 70 | 71 | // -- END OF FILE -- 72 | -------------------------------------------------------------------------------- /examples/I2C_scanner_ping_count/I2C_scanner_ping_count.ino: -------------------------------------------------------------------------------- 1 | // FILE: I2C_scanner_ping_count.ino 2 | // AUTHOR: Rob Tillaart 3 | // PURPOSE: demo 4 | // URL: https://github.com/RobTillaart/I2C_SCANNER 5 | 6 | 7 | #include "I2C_SCANNER.h" 8 | 9 | I2C_SCANNER scanner; 10 | 11 | 12 | void setup() 13 | { 14 | Serial.begin(115200); 15 | while (!Serial); 16 | Serial.println(); 17 | Serial.println(__FILE__); 18 | Serial.print("I2C_SCANNER_LIB_VERSION: "); 19 | Serial.println(I2C_SCANNER_LIB_VERSION); 20 | 21 | Wire.begin(); 22 | scanner.begin(); 23 | 24 | for (int addr = 0; addr < 128; addr++) 25 | { 26 | if (addr % 8 == 0) 27 | { 28 | Serial.println(); 29 | Serial.print("0x"); 30 | if (addr < 0x10) Serial.print("0");; 31 | Serial.print(addr, HEX); 32 | Serial.print(":\t"); 33 | } 34 | uint8_t n = scanner.ping(addr, 5); 35 | if (n > 0) 36 | { 37 | if (addr < 0x10) Serial.print("0"); 38 | Serial.print(addr, HEX); 39 | Serial.print("("); 40 | Serial.print(n); 41 | Serial.print(")"); 42 | } 43 | else 44 | { 45 | Serial.print("-"); 46 | } 47 | Serial.print("\t"); 48 | } 49 | Serial.println(); 50 | Serial.println(); 51 | 52 | // AVR only (prevents hang) 53 | // Wire.setWireTimeout(2000000); // micros. 54 | Serial.println("10K calls\tTime (us)"); 55 | uint32_t start = micros(); 56 | uint8_t addr = 0x41; 57 | uint16_t n = scanner.ping(addr, 10000); 58 | uint32_t stop = micros(); 59 | 60 | if (addr < 0x10) Serial.print("0"); 61 | Serial.print(addr, HEX); 62 | Serial.print("("); 63 | Serial.print(n); 64 | Serial.print(")\t"); 65 | Serial.print((stop - start) * 0.0001, 2); 66 | Serial.print(" per call."); 67 | Serial.println(); 68 | } 69 | 70 | 71 | void loop() 72 | { 73 | } 74 | 75 | 76 | // -- END OF FILE -- 77 | -------------------------------------------------------------------------------- /test/unit_test_001.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // FILE: unit_test_001.cpp 3 | // AUTHOR: Rob Tillaart 4 | // DATE: 2022-08-29 5 | // PURPOSE: unit tests for the I2C_SCANNER class 6 | // https://github.com/RobTillaart/I2C_SCANNER 7 | // https://github.com/Arduino-CI/arduino_ci/blob/master/REFERENCE.md 8 | // 9 | 10 | // supported assertions 11 | // ---------------------------- 12 | // assertEqual(expected, actual) 13 | // assertNotEqual(expected, actual) 14 | // assertLess(expected, actual) 15 | // assertMore(expected, actual) 16 | // assertLessOrEqual(expected, actual) 17 | // assertMoreOrEqual(expected, actual) 18 | // assertTrue(actual) 19 | // assertFalse(actual) 20 | // assertNull(actual) 21 | 22 | 23 | #include 24 | 25 | #include "Arduino.h" 26 | #include "I2C_SCANNER.h" 27 | 28 | #define A0 0 29 | 30 | 31 | unittest_setup() 32 | { 33 | fprintf(stderr, "I2C_SCANNER_LIB_VERSION: %s\n", (char *) I2C_SCANNER_LIB_VERSION); 34 | } 35 | 36 | unittest_teardown() 37 | { 38 | } 39 | 40 | 41 | unittest(test_constructor) 42 | { 43 | I2C_SCANNER scanner; 44 | assertTrue(scanner.begin()); 45 | 46 | assertTrue(scanner.setClock(200000)); 47 | } 48 | 49 | 50 | unittest(test_getWirePortCount) 51 | { 52 | I2C_SCANNER scanner; 53 | assertTrue(scanner.begin()); 54 | 55 | int ports = scanner.getWirePortCount(); 56 | fprintf(stderr, "PORTS: %d\n", ports); 57 | assertMoreOrEqual(1, ports); 58 | 59 | for (int p = 0; p < ports; p++) 60 | { 61 | assertTrue(scanner.setWire(p)); 62 | } 63 | 64 | // explicit fail 65 | assertFalse(scanner.setWire(10)); 66 | } 67 | 68 | 69 | unittest(test_scanning) 70 | { 71 | I2C_SCANNER scanner; 72 | assertTrue(scanner.begin()); 73 | 74 | // not a real test but at least something... 75 | fprintf(stderr, "PING: %d\n", scanner.ping(0x50)); 76 | fprintf(stderr, "DIAG: %d\n", scanner.diag(0x50)); 77 | // fprintf(stderr, "COUNT: %d\n", scanner.count()); 78 | // pingTime uses micros() so will block... 79 | } 80 | 81 | 82 | unittest_main() 83 | 84 | 85 | // -- END OF FILE -- 86 | -------------------------------------------------------------------------------- /I2C_SCANNER.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // 3 | // FILE: I2C_SCANNER.h 4 | // AUTHOR: Rob Tillaart 5 | // VERSION: 0.3.1 6 | // DATE: 2022-08-29 7 | // PURPOSE: Arduino class to implement an I2C scanner. 8 | // URL: https://github.com/RobTillaart/I2C_SCANNER 9 | 10 | 11 | #include "Arduino.h" 12 | #include "Wire.h" 13 | 14 | #define I2C_SCANNER_LIB_VERSION (F("0.3.1")) 15 | 16 | 17 | class I2C_SCANNER 18 | { 19 | public: 20 | 21 | I2C_SCANNER(TwoWire *wire = &Wire); 22 | 23 | // CONFIGURATION 24 | bool begin(); 25 | 26 | // I2C PORT 27 | uint8_t getWirePortCount(); 28 | // 0 == Wire, 1 = Wire1 ... 5 = Wire5 (if supported) 29 | // to be used for iteration over the I2C interfaces. 30 | bool setWire(uint8_t n = 0); 31 | bool setWire(TwoWire *wire = &Wire); 32 | TwoWire* getWire(); 33 | 34 | // valid methods 0 and 1. 35 | int softwareReset(uint8_t method = 0); 36 | 37 | // TIMING 38 | bool setClock(uint32_t clockFrequency = 100000UL); 39 | #if defined(ESP32) 40 | uint32_t getClock(); 41 | #endif 42 | 43 | // SCANNING FUNCTIONS 44 | uint16_t ping(uint8_t address, uint16_t count = 1); 45 | int diag(uint8_t address); 46 | int32_t pingTime(uint8_t address); 47 | uint8_t count(uint8_t start = 0, uint8_t end = 127); 48 | 49 | 50 | // EXPERIMENTAL. 51 | // not all platforms support this function. 52 | // patch .cpp file to get this working for your platform. 53 | // timeout in microseconds, 54 | // set timeOut = 0 to disable 55 | // reset == reset on timeout. 56 | bool setWireTimeout(uint32_t timeOut, bool reset = false); 57 | uint32_t getWireTimeout(); 58 | 59 | 60 | // EXPERIMENTAL. 61 | // will this works for non default I2C -> Wire1 etc 62 | // https://forum.arduino.cc/t/i2c-ina219-on-pico-w-esp32s3/1362956 63 | // int getSDA() { return SDA; }; 64 | // int getSCL() { return SCL; }; 65 | 66 | // EXPERIMENTAL 67 | // 24 bit values == OK 68 | // 0xFFxxxxxx == errorCode (to be elaborated) 69 | uint32_t getDeviceID(uint8_t address); 70 | 71 | 72 | private: 73 | int _init(); 74 | int _wirePortCount; 75 | TwoWire* _wire; 76 | 77 | uint32_t _timeout = 0; 78 | }; 79 | 80 | 81 | // -- END OF FILE -- 82 | 83 | -------------------------------------------------------------------------------- /I2C_SCANNER.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // FILE: I2C_SCANNER.cpp 3 | // AUTHOR: Rob Tillaart 4 | // VERSION: 0.3.1 5 | // DATE: 2022-08-29 6 | // PURPOSE: Arduino class to implement an I2C scanner. 7 | // URL: https://github.com/RobTillaart/I2C_SCANNER 8 | 9 | 10 | #include "I2C_SCANNER.h" 11 | 12 | 13 | I2C_SCANNER::I2C_SCANNER(TwoWire *wire) 14 | { 15 | _wire = wire; 16 | } 17 | 18 | 19 | // 20 | // CONFIGURATION 21 | // 22 | bool I2C_SCANNER::begin() 23 | { 24 | _init(); 25 | return true; 26 | } 27 | 28 | 29 | // 30 | // I2C PORT 31 | // 32 | uint8_t I2C_SCANNER::getWirePortCount() 33 | { 34 | return _wirePortCount; 35 | } 36 | 37 | 38 | // 0 == Wire, 1 = Wire1 etc. 39 | bool I2C_SCANNER::setWire(uint8_t n) 40 | { 41 | if (n == 0) { _wire = &Wire; return true; }; 42 | #if defined WIRE_IMPLEMENT_WIRE1 || WIRE_INTERFACES_COUNT > 1 43 | if (n == 1) { _wire = &Wire1; return true; }; 44 | #endif 45 | #if defined WIRE_IMPLEMENT_WIRE2 || WIRE_INTERFACES_COUNT > 2 46 | if (n == 2) { _wire = &Wire2; return true; }; 47 | #endif 48 | #if defined WIRE_IMPLEMENT_WIRE3 || WIRE_INTERFACES_COUNT > 3 49 | if (n == 3) { _wire = &Wire3; return true; }; 50 | #endif 51 | #if defined WIRE_IMPLEMENT_WIRE4 || WIRE_INTERFACES_COUNT > 4 52 | if (n == 4) { _wire = &Wire4; return true; }; 53 | #endif 54 | #if defined WIRE_IMPLEMENT_WIRE5 || WIRE_INTERFACES_COUNT > 5 55 | if (n == 5) { _wire = &Wire5; return true; }; 56 | #endif 57 | return false; 58 | } 59 | 60 | 61 | bool I2C_SCANNER::setWire(TwoWire *wire) 62 | { 63 | _wire = wire; 64 | return true; 65 | } 66 | 67 | 68 | TwoWire* I2C_SCANNER::getWire() 69 | { 70 | return _wire; 71 | } 72 | 73 | 74 | // 75 | // RESET 76 | // 77 | int I2C_SCANNER::softwareReset(uint8_t method) 78 | { 79 | // only support 0 and 1 80 | if (method > 1) return -999; 81 | if (method == 1) 82 | { 83 | // from https://github.com/RobTillaart/PCA9634/issues/10#issuecomment-1206326417 84 | const uint8_t SW_RESET = 0x03; 85 | _wire->beginTransmission(SW_RESET); 86 | _wire->write(0xA5); 87 | _wire->write(0x5A); 88 | return _wire->endTransmission(true); 89 | } 90 | 91 | // default 92 | // based upon NXP specification - UM10204.pdf - page 16 93 | _wire->beginTransmission(0x00); 94 | _wire->write(0x06); 95 | return _wire->endTransmission(true); 96 | } 97 | 98 | 99 | // 100 | // TIMING 101 | // 102 | bool I2C_SCANNER::setClock(uint32_t clockFrequency) 103 | { 104 | _wire->setClock(clockFrequency); 105 | return true; 106 | } 107 | 108 | 109 | #if defined(ESP32) 110 | uint32_t I2C_SCANNER::getClock() 111 | { 112 | return _wire->getClock(); 113 | } 114 | #endif 115 | 116 | 117 | // 118 | // SCANNING 119 | // 120 | uint16_t I2C_SCANNER::ping(uint8_t address, uint16_t count) 121 | { 122 | if (count == 0) count = 1; 123 | uint16_t success = 0; 124 | while (count > 0) 125 | { 126 | count--; 127 | if (diag(address) == 0) success++; 128 | } 129 | return success; 130 | } 131 | 132 | 133 | int I2C_SCANNER::diag(uint8_t address) 134 | { 135 | _wire->beginTransmission(address); 136 | int x = _wire->endTransmission(); 137 | return x; 138 | } 139 | 140 | 141 | int32_t I2C_SCANNER::pingTime(uint8_t address) 142 | { 143 | uint32_t start = micros(); 144 | int x = diag(address); 145 | int32_t duration = (int32_t)(micros() - start); 146 | if (x == 0) return duration; 147 | return -duration; 148 | } 149 | 150 | 151 | uint8_t I2C_SCANNER::count(uint8_t start, uint8_t end) 152 | { 153 | uint8_t cnt = 0; 154 | for (uint8_t addr = start; addr <= end; addr++) 155 | { 156 | if (diag(addr) == 0) cnt++; 157 | } 158 | return cnt; 159 | } 160 | 161 | 162 | bool I2C_SCANNER::setWireTimeout(uint32_t timeOut, bool reset) 163 | { 164 | if (_timeout != timeOut) 165 | { 166 | _timeout = timeOut; 167 | // not all platforms support this. 168 | #if defined(WIRE_HAS_TIMEOUT) 169 | _wire->setWireTimeout(timeOut, reset); 170 | #endif 171 | return true; 172 | } 173 | return false; 174 | } 175 | 176 | 177 | uint32_t I2C_SCANNER::getWireTimeout() 178 | { 179 | return _timeout; 180 | } 181 | 182 | 183 | //////////////////////////////////////////////////// 184 | // 185 | // PRIVATE 186 | // 187 | int I2C_SCANNER::_init() 188 | { 189 | Wire.begin(); 190 | 191 | _wirePortCount = 1; 192 | #if defined WIRE_IMPLEMENT_WIRE1 || WIRE_INTERFACES_COUNT > 1 193 | Wire1.begin(); 194 | _wirePortCount++; 195 | #endif 196 | #if defined WIRE_IMPLEMENT_WIRE2 || WIRE_INTERFACES_COUNT > 2 197 | Wire2.begin(); 198 | _wirePortCount++; 199 | #endif 200 | #if defined WIRE_IMPLEMENT_WIRE3 || WIRE_INTERFACES_COUNT > 3 201 | Wire3.begin(); 202 | _wirePortCount++; 203 | #endif 204 | #if defined WIRE_IMPLEMENT_WIRE4 || WIRE_INTERFACES_COUNT > 4 205 | Wire4.begin(); 206 | _wirePortCount++; 207 | #endif 208 | #if defined WIRE_IMPLEMENT_WIRE5 || WIRE_INTERFACES_COUNT > 5 209 | Wire5.begin(); 210 | _wirePortCount++; 211 | #endif 212 | return _wirePortCount; 213 | } 214 | 215 | 216 | uint32_t I2C_SCANNER::getDeviceID(uint8_t address) 217 | { 218 | uint8_t DEVICEID_ADDRESS = 0x7C; 219 | _wire->beginTransmission(DEVICEID_ADDRESS); 220 | _wire->write(address << 1); // address shifted one bit 221 | int code = _wire->endTransmission(false); // explicit send a restart. 222 | if (code != 0) 223 | { 224 | return 0xFF000000 + code; 225 | } 226 | if (_wire->requestFrom(DEVICEID_ADDRESS, (uint8_t)3) != 3) 227 | { 228 | return 0xFFFFFFFF; 229 | } 230 | // build up return code 231 | uint32_t deviceId = _wire->read(); 232 | deviceId <<= 8; 233 | deviceId |= _wire->read(); 234 | deviceId <<= 8; 235 | deviceId |= _wire->read(); 236 | return deviceId; 237 | } 238 | 239 | 240 | // -- END OF FILE -- 241 | 242 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Arduino CI](https://github.com/RobTillaart/I2C_SCANNER/workflows/Arduino%20CI/badge.svg)](https://github.com/marketplace/actions/arduino_ci) 3 | [![Arduino-lint](https://github.com/RobTillaart/I2C_SCANNER/actions/workflows/arduino-lint.yml/badge.svg)](https://github.com/RobTillaart/I2C_SCANNER/actions/workflows/arduino-lint.yml) 4 | [![JSON check](https://github.com/RobTillaart/I2C_SCANNER/actions/workflows/jsoncheck.yml/badge.svg)](https://github.com/RobTillaart/I2C_SCANNER/actions/workflows/jsoncheck.yml) 5 | [![GitHub issues](https://img.shields.io/github/issues/RobTillaart/I2C_SCANNER.svg)](https://github.com/RobTillaart/I2C_SCANNER/issues) 6 | 7 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/RobTillaart/I2C_SCANNER/blob/master/LICENSE) 8 | [![GitHub Release](https://img.shields.io/github/release/RobTillaart/I2C_SCANNER.svg?maxAge=3600)](https://github.com/RobTillaart/I2C_SCANNER/releases) 9 | [![PlatformIO Registry](https://badges.registry.platformio.org/packages/robtillaart/library/I2C_SCANNER.svg)](https://registry.platformio.org/libraries/robtillaart/I2C_SCANNER) 10 | 11 | 12 | # I2C_SCANNER 13 | 14 | Arduino library to implement an I2C scanner. 15 | 16 | 17 | ## Description 18 | 19 | I2C_SCANNER is a class to build an I2C scanner, either minimal or more complex. 20 | 21 | The class provides different functions to analyse the connectivity of devices 22 | on the I2C bus. There are functions to adjust the frequency and functions to 23 | select the bus in case of multiple I2C ports. 24 | 25 | A typical other use is to see if all devices are available (breadboard test). 26 | 27 | Furthermore there are different functions to scan the I2C port, 28 | see the section scanning below. 29 | 30 | The address range for "normal" I2C devices is from 9 to 119. 31 | The user may use other values for address at his own risk. 32 | 33 | If there is missing functionality in this library, please file an issue. 34 | 35 | 36 | ### 0.3.0 Breaking change 37 | 38 | Version 0.3.0 introduced a breaking change. 39 | You cannot set the pins in **begin()** any more. 40 | This reduces the dependency of processor dependent Wire implementations. 41 | The user has to call **Wire.begin()** and can optionally set the Wire pins 42 | before calling **begin()**. 43 | 44 | 45 | ### Testing 46 | 47 | The library is tested with the following boards: 48 | 49 | | board | works | 50 | |:---------:|:-------:| 51 | | UNO | yes | 52 | | MEGA | yes | 53 | | NANO | ? | 54 | | ESP8266 | ? | 55 | | ESP32 | yes | 56 | 57 | Please file an issue if your board does work (or not). 58 | 59 | 60 | ### Related 61 | 62 | - https://github.com/RobTillaart/I2C_SCANNER 63 | - https://github.com/RobTillaart/I2C_SOFTRESET 64 | - https://github.com/RobTillaart/MultiSpeedI2CScanner 65 | 66 | 67 | ## Interface 68 | 69 | ```cpp 70 | #include "I2C_SCANNER.h" 71 | ``` 72 | 73 | ### Constructor 74 | 75 | - **I2C_SCANNER(TwoWire \*wire = &Wire)** Constructor with the default Wire I2C bus. 76 | - **bool begin()** To start the Wire library. 77 | 78 | 79 | ### Configuration 80 | 81 | - **bool setClock(uint32_t clockFrequency = 100000UL)** sets the speed of the I2C bus. 82 | Returns true. 83 | Note the supported frequency is board dependent. Check your boards datasheet. 84 | - **uint32_t getClock()** supported by some platforms, including ESP32. 85 | Please fill in issue if your board is missing here. 86 | - **uint8_t getWireCount()** returns the number of Wire ports (hardware I2C). 87 | To be used by **setWire(uint8_t n)**. 88 | - **bool setWire(uint8_t n)** sets the Wire port by number. 89 | Assumes there exist Wire, Wire1 ... Wire5. 90 | The number n should not exceed the value returned by **getWireCount()** 91 | - **bool setWire(TwoWire \*wire = &Wire)** set a Wire port by 'name' e.g. Wire1. 92 | - **TwoWire \* getWire()** returns the Wire object set. 93 | 94 | 95 | ### Scanning 96 | 97 | - **uint16_t ping(uint8_t address, uint16_t count = 1)** Tries to make contact with I2C address. 98 | Returns the number of successful "contacts / connections" with the address. 99 | Typical number of retries is 1..5, however one can also do a long test up to 65535 retries. 100 | Can be used for endurance test / diagnosis of responsiveness. 101 | Note the function does not call **yield()** intern so use with care. 102 | Note that when count is large, the function call will block for a long time. 103 | Better use a loop of e.g. 100 retries at a time. 104 | - **int diag(uint8_t address)** Tries to make contact with I2C address. 105 | Returns Wire status code 0 == OK, other return values might depend on platform used. 106 | - **int32_t pingTime(uint8_t address)** Tries to make contact with I2C address. 107 | Returns time used in micros. Returns a negative time (< 0) if failed to contact. 108 | - **uint8_t count(uint8_t start = 0, uint8_t end = 127)** pings address range. 109 | Includes start and end address too. Returns the number of addresses found. 110 | 111 | 112 | ### Reset 113 | 114 | (needs testing) 115 | 116 | - **int softwareReset(uint8_t method = 0)** sends a I2C SWRST command over the configured I2C bus. 117 | This will cause all devices that support this command to do a "power on" reset. 118 | The code implements two methods, 119 | - 0 = NXP spec (default) 120 | - 1 = from PCA9634 issue. 121 | 122 | The **softwareReset()** function should return 0 for success. 123 | The value -999 indicates invalid method selected. 124 | Other are I2C specific error codes. 125 | 126 | 127 | ### Timeout 128 | 129 | Experimental 130 | 131 | Not all platforms support this functionality, you need to patch I2C_SCANNER.cpp file to get 132 | this working for your platform. 133 | - **bool setWireTimeout(uint32_t timeOut)** 134 | - **uint32_t getWireTimeout()** 135 | 136 | 137 | ### getDeviceID 138 | 139 | Experimental (to be confirmed) 140 | 141 | - **uint32_t getDeviceID(uint8_t address)** Returns 24 bits, see table below. 142 | Returns 0xFFxxxxxx in case of an error, board dependent. 143 | 144 | | bits | meaning | notes | 145 | |:--------:|:---------------|:--------| 146 | | 00..02 | Revision | 147 | | 03..08 | Feature | 148 | | 09..15 | Category | 149 | | 16..23 | Manufacturer | no details known 150 | | 24..31 | error flag | 0xFFxxxxxx 151 | 152 | TODO: need to be split, or helper functions e.g. **uint8_t revision()** 153 | 154 | No known fields yet. 155 | 156 | 157 | ## Future ideas 158 | 159 | #### Must 160 | 161 | - documentation. 162 | 163 | #### Should 164 | 165 | 166 | #### Could 167 | 168 | - investigate ESP8266 does have a **setClockStretchLimit(timeout)** 169 | - add **bool hardwareReset()** 170 | - keep data HIGH for X clock pulses - google this. 171 | - (needs investigation) 172 | - https://github.com/esp8266/Arduino/issues/1025 173 | - read the state of the I2C pins 174 | - **uint8_t readSDA()** diagnose the I2C bus. Board specific! 175 | - **uint8_t readSCL()** diagnose the I2C bus. Board specific! 176 | - implement **getClock()** for AVR based platforms 177 | - reverse calculate TWBR and pre-scaler. 178 | - needs investigation 179 | - support for RP2040 180 | - needs investigation 181 | - add table with default I2C pins per platform / board 182 | - https://www.arduino.cc/reference/en/language/functions/communication/wire/ 183 | 184 | 185 | #### Won't 186 | 187 | - device analysis 188 | - add **bool send(address, uint8_t \*buffer, uint8_t length)** 189 | - add **bool receive(address, uint8_t \*buffer, uint8_t length)** 190 | - add iterator 191 | - **uint8_t first(uint8_t start = 0)** returns address or 255 192 | - **uint8_t next()** returns address or 255. 193 | - **uint8_t \_devices[16]** cache hits ? 194 | - No 195 | - implement a **SW_I2C** 196 | - No, user may use a SW_I2C that derives from TwoWire. 197 | 198 | 199 | ## Support 200 | 201 | If you appreciate my libraries, you can support the development and maintenance. 202 | Improve the quality of the libraries by providing issues and Pull Requests, or 203 | donate through PayPal or GitHub sponsors. 204 | 205 | Thank you, 206 | 207 | --------------------------------------------------------------------------------