├── README.md ├── Pin_Tester └── Pin_Tester.ino ├── I2C_Sensor_Detector └── I2C_Sensor_Detector.ino ├── I2C_Scanner └── I2C_Scanner.ino └── NTP_PPS_Server └── NTP_PPS_Server.ino /README.md: -------------------------------------------------------------------------------- 1 | Auto-detecting hardware isn’t just a neat trick—it makes our projects smarter, simpler, and more user-friendly. It’s a win for both users and developers. Let’s see how we can build it. 2 | And I built an NTP server with an ESP32. No need for a Raspberry Pi anymore... 3 | 4 | This is the repo for video: https://youtu.be/JsCbmo9WfCM 5 | -------------------------------------------------------------------------------- /Pin_Tester/Pin_Tester.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Pin Connection Test Sketch 3 | This sketch defines a function isPinConnected() that tests a pin using both an 4 | internal pull-up and pull-down resistor. It returns true if: 5 | - With pull-up: the pin, which normally reads HIGH, is pulled LOW. 6 | - With pull-down: the pin, which normally reads LOW, is pulled HIGH. 7 | The sketch then calls this function and prints the result to the Serial Monitor. 8 | */ 9 | 10 | bool isPinConnected(int pin) { 11 | bool connected = false; 12 | unsigned long startTime; 13 | 14 | pinMode(pin, INPUT_PULLUP); 15 | startTime = millis(); 16 | while (millis() - startTime < 1000) { // Check for 1 second. 17 | if (digitalRead(pin) == LOW) { 18 | connected = true; 19 | break; 20 | } 21 | } 22 | if (!connected) { 23 | pinMode(pin, INPUT_PULLDOWN); 24 | startTime = millis(); 25 | while (millis() - startTime < 1000) { // Check for 1 second. 26 | if (digitalRead(pin) == HIGH) { 27 | connected = true; 28 | break; 29 | } 30 | } 31 | } 32 | return connected; 33 | } 34 | 35 | void setup() { 36 | Serial.begin(115200); 37 | int testPin = 15; // Replace with the desired pin number. 38 | 39 | Serial.println("Starting Pin Connection Test..."); 40 | 41 | // Test if something is connected to the testPin. 42 | if (isPinConnected(testPin)) { 43 | Serial.println("Something is connected to the pin."); 44 | } else { 45 | Serial.println("Nothing detected on the pin."); 46 | } 47 | } 48 | 49 | void loop() { 50 | // Nothing to do in loop for this simple test. 51 | } 52 | -------------------------------------------------------------------------------- /I2C_Sensor_Detector/I2C_Sensor_Detector.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // Create sensor objects 8 | Adafruit_BMP280 bmp280; // BMP280 object 9 | Adafruit_BME280 bme280; // BME280 object 10 | Adafruit_BME680 bme680; // BME680 object 11 | Adafruit_MCP9808 mcp9808; // MCP9808 object 12 | 13 | // An enum to keep track of which sensor was found 14 | enum SensorType { 15 | SENSOR_NONE, 16 | SENSOR_BMP280, 17 | SENSOR_BME280, 18 | SENSOR_BME680, 19 | SENSOR_MCP9808 20 | }; 21 | 22 | // We'll store which sensor we found (if any) 23 | SensorType foundSensor = SENSOR_NONE; 24 | 25 | // Function to detect the sensor 26 | SensorType detectSensor() { 27 | if (bmp280.begin(0x76) || bmp280.begin(0x77)) { 28 | Serial.println("BMP280 sensor detected"); 29 | return SENSOR_BMP280; 30 | } 31 | else if (bme280.begin(0x76) || bme280.begin(0x77)) { 32 | Serial.println("BME280 sensor detected"); 33 | return SENSOR_BME280; 34 | } 35 | else if (bme680.begin(0x76) || bme680.begin(0x77)) { 36 | Serial.println("BME680 sensor detected"); 37 | return SENSOR_BME680; 38 | } 39 | else if (mcp9808.begin(0x18)) { 40 | Serial.println("MCP9808 sensor detected"); 41 | return SENSOR_MCP9808; 42 | } 43 | else { 44 | Serial.println("No known sensor found."); 45 | return SENSOR_NONE; 46 | } 47 | } 48 | 49 | void setup() { 50 | Serial.begin(115200); 51 | Wire.begin(); 52 | delay(1000); // Give the bus some time to settle 53 | 54 | Serial.println("Starting sensor detection..."); 55 | foundSensor = detectSensor(); 56 | Serial.println("Sensor detection finished."); 57 | } 58 | 59 | void loop() { 60 | if (foundSensor == SENSOR_BMP280) { 61 | float temperature = bmp280.readTemperature(); 62 | Serial.print("BMP280 Temperature: "); 63 | Serial.print(temperature); 64 | Serial.println(" °C"); 65 | } 66 | else if (foundSensor == SENSOR_BME280) { 67 | float temperature = bme280.readTemperature(); 68 | Serial.print("BME280 Temperature: "); 69 | Serial.print(temperature); 70 | Serial.println(" °C"); 71 | } 72 | else if (foundSensor == SENSOR_BME680) { 73 | // BME680 typically needs a "performReading()" call to update data 74 | if (bme680.performReading()) { 75 | float temperature = bme680.temperature; 76 | Serial.print("BME680 Temperature: "); 77 | Serial.print(temperature); 78 | Serial.println(" °C"); 79 | } else { 80 | Serial.println("BME680: Failed to perform reading"); 81 | } 82 | } 83 | else if (foundSensor == SENSOR_MCP9808) { 84 | float temperature = mcp9808.readTempC(); 85 | Serial.print("MCP9808 Temperature: "); 86 | Serial.print(temperature); 87 | Serial.println(" °C"); 88 | } 89 | else { 90 | // No sensor found, do nothing or print a message 91 | Serial.println("No sensor to read from."); 92 | } 93 | 94 | delay(2000); // Wait 2 seconds between reads 95 | } -------------------------------------------------------------------------------- /I2C_Scanner/I2C_Scanner.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Structure to hold a device address and its friendly name. 4 | struct I2CDevice { 5 | uint8_t address; 6 | const char* name; 7 | }; 8 | 9 | // Lookup table based on Adafruit's I2C address list. 10 | // This table includes common temperature sensors, BMP/BME sensors, and the BMP085. 11 | const I2CDevice knownDevices[] = { 12 | // Temperature Sensors: 13 | { 0x18, "MCP9808 Temperature Sensor" }, 14 | { 0x40, "HTU21D/Si7021 Temp/Humidity Sensor" }, 15 | { 0x41, "Alternate HTU21D/Si7021 Temp/Humidity Sensor" }, 16 | { 0x48, "TMP102/LM75 Temperature Sensor (or ADS1115 ADC)" }, 17 | { 0x49, "TMP102/LM75 Temperature Sensor (or ADS1115 ADC)" }, 18 | { 0x4A, "TMP102/LM75 Temperature Sensor (or ADS1115 ADC)" }, 19 | { 0x4B, "TMP102/LM75 Temperature Sensor (or ADS1115 ADC)" }, 20 | { 0x5A, "MLX90614 Infrared Temperature Sensor" }, 21 | { 0x5B, "MLX90614 Infrared Temperature Sensor (Alternate)" }, 22 | 23 | // Barometric and Environmental Sensors: 24 | { 0x76, "BMP280/BME280 Barometric Pressure Sensor" }, 25 | { 0x77, "BMP085/BMP180/BMP280/BME280 Barometric Pressure Sensor" }, 26 | 27 | // Other Devices: 28 | { 0x27, "PCF8574 I/O Expander (commonly for I2C LCD)" }, 29 | { 0x3C, "SSD1306 OLED Display" }, 30 | { 0x68, "MPU6050 Gyro/Accelerometer or DS1307 RTC" }, 31 | { 0x69, "Alternate for MPU6050 Gyro/Accelerometer" } 32 | }; 33 | const int knownDevicesCount = sizeof(knownDevices) / sizeof(knownDevices[0]); 34 | 35 | // Function to scan the I2C bus and print device details. 36 | void scanI2CBus() { 37 | Serial.println("\nScanning I2C bus..."); 38 | byte error, address; 39 | int devicesFound = 0; 40 | 41 | // Loop through all possible I2C addresses (1-126) 42 | for (address = 1; address < 127; address++) { 43 | Wire.beginTransmission(address); 44 | error = Wire.endTransmission(); 45 | 46 | if (error == 0) { // device responded at this address 47 | Serial.print("I2C device found at address 0x"); 48 | if (address < 16) Serial.print("0"); 49 | Serial.print(address, HEX); 50 | Serial.print(" -> "); 51 | 52 | // Check if the found device matches any in our lookup table. 53 | bool known = false; 54 | for (int i = 0; i < knownDevicesCount; i++) { 55 | if (knownDevices[i].address == address) { 56 | Serial.println(knownDevices[i].name); 57 | known = true; 58 | break; 59 | } 60 | } 61 | if (!known) { 62 | Serial.println("Unknown device"); 63 | } 64 | devicesFound++; 65 | delay(10); // Small delay for stability 66 | } 67 | } 68 | 69 | // Print summary 70 | if (devicesFound == 0) { 71 | Serial.println("No I2C devices found"); 72 | } else { 73 | Serial.print("Scan complete, "); 74 | Serial.print(devicesFound); 75 | Serial.println(" device(s) found."); 76 | } 77 | } 78 | 79 | void setup() { 80 | Serial.begin(115200); 81 | while (!Serial) { 82 | ; // Wait for Serial port to initialize. 83 | } 84 | 85 | 86 | /* 87 | int resetPin = 16; 88 | Serial.print("Toggling OLED reset pin: "); 89 | Serial.println(resetPin); 90 | pinMode(resetPin, OUTPUT); 91 | digitalWrite(resetPin, LOW); 92 | delay(50); // Hold low for 50ms 93 | digitalWrite(resetPin, HIGH); 94 | delay(50); // Wait 50ms after releasing 95 | */ 96 | 97 | Serial.println("\nI2C Scanner with Device Lookup encapsulated in a function"); 98 | //Wire.begin(4, 15); 99 | Wire.begin(); 100 | } 101 | 102 | void loop() { 103 | scanI2CBus(); 104 | delay(5000); // Wait 5 seconds before the next scan 105 | } 106 | -------------------------------------------------------------------------------- /NTP_PPS_Server/NTP_PPS_Server.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Debugging levels 11 | #define DEBUG_STANDARD 1 12 | #define DEBUG_TIME_CRITICAL 0 13 | 14 | // Debugging Macros 15 | #if DEBUG_STANDARD 16 | #define DEBUG_PRINT(x) Serial.print(x) 17 | #define DEBUG_PRINTLN(x) Serial.println(x) 18 | #else 19 | #define DEBUG_PRINT(x) 20 | #define DEBUG_PRINTLN(x) 21 | #endif 22 | 23 | #if DEBUG_TIME_CRITICAL 24 | #define DEBUG_CRITICAL_PRINT(x) Serial.print(x) 25 | #define DEBUG_CRITICAL_PRINTLN(x) Serial.println(x) 26 | #else 27 | #define DEBUG_CRITICAL_PRINT(x) 28 | #define DEBUG_CRITICAL_PRINTLN(x) 29 | #endif 30 | 31 | // Global Variables 32 | TinyGPSPlus gps; 33 | SemaphoreHandle_t gpsMutex; 34 | WiFiUDP udp; 35 | 36 | volatile bool PPSsignal = false; 37 | volatile bool PPSavailable = false; 38 | 39 | HardwareSerial gpsSerial(1); 40 | 41 | // Baud rate for GPS and correction factor for time update 42 | #define GPSBaud 9600 43 | #define CORRECTION_FACTOR 0 44 | 45 | #define EEPROM_SIZE 1 46 | 47 | // Board configuration structure including PPS and GPS pins. 48 | struct BoardConfig { 49 | const char *boardName; 50 | eth_phy_type_t phyType; 51 | int ethAddr; 52 | int powerPin; 53 | int mdcPin; 54 | int mdioPin; 55 | eth_clock_mode_t clkMode; 56 | int rxPin; // GPS RX pin 57 | int txPin; // GPS TX pin 58 | int ppsPin; // PPS signal pin 59 | }; 60 | 61 | // Define board configurations for different boards. 62 | BoardConfig boardConfigs[] = { 63 | { "LilyGo PoE", ETH_PHY_LAN8720, 0, -1, 23, 18, ETH_CLOCK_GPIO17_OUT, 15, 4, 14 }, 64 | { "WT32-eth01", ETH_PHY_LAN8720, 1, 16, 23, 18, ETH_CLOCK_GPIO0_IN, 15, 4, 14 } 65 | }; 66 | const int numBoards = sizeof(boardConfigs) / sizeof(boardConfigs[0]); 67 | 68 | // Global variable to store the selected board configuration. 69 | BoardConfig currentBoardConfig; 70 | 71 | int findBoardConfig() { 72 | if (!EEPROM.begin(EEPROM_SIZE)) { 73 | DEBUG_PRINTLN("Failed to initialize EEPROM."); 74 | return -1; 75 | } 76 | int lastTested = EEPROM.read(0); 77 | int currentBoard = (lastTested + 1) % numBoards; 78 | 79 | // Save the current configuration globally. 80 | currentBoardConfig = boardConfigs[currentBoard]; 81 | DEBUG_PRINT("Initializing Ethernet for "); 82 | DEBUG_PRINTLN(currentBoardConfig.boardName); 83 | 84 | pinMode(currentBoardConfig.powerPin, OUTPUT); 85 | digitalWrite(currentBoardConfig.powerPin, LOW); 86 | delay(100); 87 | digitalWrite(currentBoardConfig.powerPin, HIGH); 88 | delay(100); 89 | 90 | bool ethStarted = ETH.begin(currentBoardConfig.phyType, 91 | currentBoardConfig.ethAddr, 92 | currentBoardConfig.mdcPin, 93 | currentBoardConfig.mdioPin, 94 | currentBoardConfig.powerPin, 95 | currentBoardConfig.clkMode); 96 | 97 | if (!ethStarted) { 98 | DEBUG_PRINT("Ethernet initialization failed for "); 99 | DEBUG_PRINTLN(currentBoardConfig.boardName); 100 | EEPROM.write(0, currentBoard); 101 | EEPROM.commit(); 102 | ESP.restart(); 103 | } 104 | 105 | DEBUG_PRINT("Ethernet initialized successfully for "); 106 | DEBUG_PRINTLN(currentBoardConfig.boardName); 107 | 108 | while (ETH.localIP() == IPAddress(0, 0, 0, 0)) { 109 | DEBUG_PRINTLN("Waiting for IP address..."); 110 | delay(500); 111 | } 112 | 113 | DEBUG_PRINT("Connected! IP Address: "); 114 | DEBUG_PRINTLN(ETH.localIP().toString().c_str()); 115 | EEPROM.write(0, 0); 116 | EEPROM.commit(); 117 | udp.begin(123); // NTP uses port 123 118 | return 0; 119 | } 120 | 121 | void IRAM_ATTR PPS_ISR() { 122 | PPSsignal = true; 123 | } 124 | 125 | void initPPS() { 126 | // Use the PPS pin from the current board configuration. 127 | pinMode(currentBoardConfig.ppsPin, INPUT_PULLDOWN); 128 | DEBUG_PRINTLN("Checking PPS signal..."); 129 | unsigned long startTime = millis(); 130 | PPSavailable = false; 131 | 132 | // Wait up to 1000 milliseconds for a HIGH signal. 133 | while (millis() - startTime < 1000) { 134 | if (digitalRead(currentBoardConfig.ppsPin) == HIGH) { 135 | PPSavailable = true; 136 | break; 137 | } 138 | } 139 | 140 | if (PPSavailable) { 141 | DEBUG_PRINTLN("PPS signal detected."); 142 | attachInterrupt(digitalPinToInterrupt(currentBoardConfig.ppsPin), PPS_ISR, FALLING); 143 | DEBUG_PRINTLN("PPS initialized. Waiting for signal..."); 144 | } else { 145 | DEBUG_PRINTLN("PPS signal not detected! Stopping program."); 146 | // Stop the program by entering an infinite loop. 147 | while (true) { 148 | // Optionally, you could add a watchdog reset or low-power sleep here. 149 | } 150 | } 151 | } 152 | 153 | 154 | void initGPS() { 155 | DEBUG_PRINTLN("Initializing GPS..."); 156 | // Use the RX/TX pins from the current board configuration. 157 | gpsSerial.begin(GPSBaud, SERIAL_8N1, currentBoardConfig.rxPin, currentBoardConfig.txPin); 158 | unsigned long startTime = millis(); 159 | while (millis() - startTime < 30000) { 160 | while (gpsSerial.available()) { 161 | gps.encode(gpsSerial.read()); 162 | if (gps.time.isValid()) { 163 | DEBUG_PRINT("GPS time acquired: "); 164 | // Format the time as HH:MM:SS, adding a leading zero if needed 165 | if (gps.time.hour() < 10) DEBUG_PRINT("0"); 166 | DEBUG_PRINT(gps.time.hour()); 167 | DEBUG_PRINT(":"); 168 | if (gps.time.minute() < 10) DEBUG_PRINT("0"); 169 | DEBUG_PRINT(gps.time.minute()); 170 | DEBUG_PRINT(":"); 171 | if (gps.time.second() < 10) DEBUG_PRINT("0"); 172 | DEBUG_PRINTLN(gps.time.second()); 173 | return; 174 | } 175 | } 176 | delay(100); 177 | } 178 | DEBUG_PRINTLN("Failed to acquire valid GPS time!"); 179 | } 180 | 181 | void readGPSTime() { 182 | while (gpsSerial.available()) { 183 | gps.encode(gpsSerial.read()); 184 | } 185 | 186 | if (gps.time.isValid() && gps.date.isValid()) { 187 | struct tm timeinfo = { 0 }; 188 | timeinfo.tm_year = gps.date.year() - 1900; 189 | timeinfo.tm_mon = gps.date.month() - 1; 190 | timeinfo.tm_mday = gps.date.day(); 191 | timeinfo.tm_hour = gps.time.hour(); 192 | timeinfo.tm_min = gps.time.minute(); 193 | timeinfo.tm_sec = gps.time.second(); 194 | 195 | time_t unixTime = mktime(&timeinfo) + 2; 196 | 197 | // Wait for the next PPS signal. 198 | while (!PPSsignal) {} 199 | 200 | struct timeval tv; 201 | tv.tv_sec = unixTime; 202 | tv.tv_usec = CORRECTION_FACTOR; 203 | settimeofday(&tv, NULL); 204 | 205 | DEBUG_CRITICAL_PRINTLN("GPS Time Updated"); 206 | 207 | PPSsignal = false; 208 | } 209 | } 210 | 211 | void handleNTPRequest() { 212 | // Check if a packet has been received 213 | int packetSize = udp.parsePacket(); 214 | if (packetSize >= 48) { // NTP packets are 48 bytes long 215 | uint8_t requestBuffer[48]; 216 | uint8_t responseBuffer[48]; 217 | 218 | // Read the incoming packet into requestBuffer 219 | udp.read(requestBuffer, 48); 220 | 221 | // Record the Receive Timestamp immediately upon packet arrival. 222 | struct timeval tv; 223 | gettimeofday(&tv, NULL); 224 | uint32_t recvSec = tv.tv_sec + 2208988800UL; 225 | uint32_t recvFrac = (uint32_t)((double)tv.tv_usec * (4294967296.0 / 1000000.0)); 226 | 227 | // --- Build the response packet --- 228 | responseBuffer[0] = 0x24; // LI=0, VN=4, Mode=4 (server) 229 | responseBuffer[1] = 1; // Stratum 1 230 | responseBuffer[2] = requestBuffer[2]; // Poll interval copied from request 231 | responseBuffer[3] = -6; // Precision (example) 232 | 233 | // Clear Root Delay & Root Dispersion 234 | for (int i = 4; i < 12; i++) { 235 | responseBuffer[i] = 0; 236 | } 237 | 238 | // Reference Identifier ("LOCL") 239 | responseBuffer[12] = 'L'; 240 | responseBuffer[13] = 'O'; 241 | responseBuffer[14] = 'C'; 242 | responseBuffer[15] = 'L'; 243 | 244 | // Reference Timestamp 245 | responseBuffer[16] = (recvSec >> 24) & 0xFF; 246 | responseBuffer[17] = (recvSec >> 16) & 0xFF; 247 | responseBuffer[18] = (recvSec >> 8) & 0xFF; 248 | responseBuffer[19] = recvSec & 0xFF; 249 | responseBuffer[20] = (recvFrac >> 24) & 0xFF; 250 | responseBuffer[21] = (recvFrac >> 16) & 0xFF; 251 | responseBuffer[22] = (recvFrac >> 8) & 0xFF; 252 | responseBuffer[23] = recvFrac & 0xFF; 253 | 254 | // Originate Timestamp: copy client's Transmit Timestamp from request (offset 40) 255 | for (int i = 0; i < 8; i++) { 256 | responseBuffer[24 + i] = requestBuffer[40 + i]; 257 | } 258 | 259 | // Receive Timestamp 260 | responseBuffer[32] = (recvSec >> 24) & 0xFF; 261 | responseBuffer[33] = (recvSec >> 16) & 0xFF; 262 | responseBuffer[34] = (recvSec >> 8) & 0xFF; 263 | responseBuffer[35] = recvSec & 0xFF; 264 | responseBuffer[36] = (recvFrac >> 24) & 0xFF; 265 | responseBuffer[37] = (recvFrac >> 16) & 0xFF; 266 | responseBuffer[38] = (recvFrac >> 8) & 0xFF; 267 | responseBuffer[39] = recvFrac & 0xFF; 268 | 269 | // Transmit Timestamp (recorded just before sending) 270 | gettimeofday(&tv, NULL); 271 | uint32_t txSec = tv.tv_sec + 2208988800UL; 272 | uint32_t txFrac = (uint32_t)((double)tv.tv_usec * (4294967296.0 / 1000000.0)); 273 | responseBuffer[40] = (txSec >> 24) & 0xFF; 274 | responseBuffer[41] = (txSec >> 16) & 0xFF; 275 | responseBuffer[42] = (txSec >> 8) & 0xFF; 276 | responseBuffer[43] = txSec & 0xFF; 277 | responseBuffer[44] = (txFrac >> 24) & 0xFF; 278 | responseBuffer[45] = (txFrac >> 16) & 0xFF; 279 | responseBuffer[46] = (txFrac >> 8) & 0xFF; 280 | responseBuffer[47] = txFrac & 0xFF; 281 | 282 | // Send the response packet 283 | udp.beginPacket(udp.remoteIP(), udp.remotePort()); 284 | udp.write(responseBuffer, 48); 285 | udp.endPacket(); 286 | 287 | DEBUG_PRINTLN("NTP response sent."); 288 | } 289 | } 290 | 291 | void gpsTask(void *parameter) { 292 | while (1) { 293 | readGPSTime(); 294 | vTaskDelay(pdMS_TO_TICKS(10)); 295 | } 296 | } 297 | 298 | void ntpTask(void *parameter) { 299 | while (1) { 300 | handleNTPRequest(); 301 | vTaskDelay(pdMS_TO_TICKS(100)); 302 | } 303 | } 304 | 305 | void setup() { 306 | Serial.begin(115200); 307 | findBoardConfig(); 308 | initPPS(); 309 | initGPS(); 310 | gpsMutex = xSemaphoreCreateMutex(); 311 | xTaskCreatePinnedToCore(gpsTask, "GPSTask", 4096, NULL, 1, NULL, 0); 312 | xTaskCreatePinnedToCore(ntpTask, "NTPTask", 4096, NULL, 1, NULL, 0); 313 | } 314 | 315 | void loop() { 316 | } 317 | --------------------------------------------------------------------------------