diff --git a/platformio.ini b/platformio.ini index 4d2a95e0a2..9f69bf67bd 100644 --- a/platformio.ini +++ b/platformio.ini @@ -368,7 +368,6 @@ build_flags = -g -DARDUINO_ARCH_ESP32S2 -DCONFIG_IDF_TARGET_ESP32S2=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 - -DCO -DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 ! ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT @@ -388,7 +387,6 @@ build_flags = -g -DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32C3 -DCONFIG_IDF_TARGET_ESP32C3=1 - -DCO -DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3 ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT @@ -411,7 +409,6 @@ build_flags = -g -DARDUINO_ARCH_ESP32S3 -DCONFIG_IDF_TARGET_ESP32S3=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_DFU_ON_BOOT=0 - -DCO ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT ${esp32_idf_V5.build_flags} @@ -436,7 +433,6 @@ build_flags = -g -DARDUINO_ARCH_ESP32C5 -DCONFIG_IDF_TARGET_ESP32C5=1 -D CONFIG_ASYNC_TCP_USE_WDT=0 - -DCO -D WLED_DISABLE_INFRARED ;; library causes boot problems on -C5 -D WLED_DISABLE_ESPNOW ;; ToDO: ESP-NOW support not yet tested ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: @@ -467,7 +463,6 @@ build_flags = -g -DARDUINO_ARCH_ESP32C6 -DCONFIG_IDF_TARGET_ESP32C6=1 -D CONFIG_ASYNC_TCP_USE_WDT=0 - -DCO ;; -D WLED_DISABLE_INFRARED ;; library not tested yet with -C6 -D WLED_DISABLE_ESPNOW ;; ToDO: temporarily disabled, until we find a solution for esp-now build errors with -C6 -DARDUINO_USB_MODE=1 ;; this flag is - most likely - mandatory for ESP32-C6 @@ -499,7 +494,6 @@ build_flags = -g -DCONFIG_IDF_TARGET_ESP32P4=1 -DBOARD_HAS_PSRAM ;; all P4 boards have PSRAM support -D CONFIG_ASYNC_TCP_USE_WDT=0 - -DCO -DARDUINO_USB_MODE=1 ;; this flag is - most likely - mandatory for ESP32-P4 ;;-D WLED_DISABLE_INFRARED ;; library not tested yet with -P4 -D WLED_DISABLE_ESPNOW ;; ESP-NOW is not possible on P4 (no native Wi-Fi radio) @@ -778,8 +772,7 @@ board = esp32dev platform = ${esp32_idf_V5.platform} platform_packages = ${esp32_idf_V5.platform_packages} build_unflags = ${esp32_idf_V5.build_unflags} -;;custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds -custom_usermods = ;; AR usermod causes bootloops on classic esp32 +custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds build_flags = ${common.build_flags} ${esp32_idf_V5.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 lib_deps = ${esp32_idf_V5.lib_deps} @@ -793,7 +786,7 @@ extends = env:esp32dev ;;build_type = debug ;; disabled - leads to stack smashing in ArduinoJSON "Unhandled debug exception: Stack canary watchpoint triggered (loopTask)" monitor_filters = esp32_exception_decoder upload_speed = 921600 -custom_usermods = ;; audioreactive usermod crashes on classic esp32 +custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds build_unflags = ${esp32_idf_V5.build_unflags} -D WLED_RELEASE_NAME=\"ESP32\" ;; -DCORE_DEBUG_LEVEL=0 @@ -814,8 +807,7 @@ lib_ignore = ${esp32_idf_V5.lib_ignore} board = esp32dev platform = ${esp32_idf_V5.platform} platform_packages = ${esp32_idf_V5.platform_packages} -;;custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds -custom_usermods = ;; AR usermod causes bootloops on classic esp32 +custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds build_unflags = ${esp32_idf_V5.build_unflags} build_flags = ${common.build_flags} ${esp32_idf_V5.build_flags} -D WLED_RELEASE_NAME=\"ESP32_8M\" #-D WLED_DISABLE_BROWNOUT_DET -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 @@ -831,8 +823,7 @@ board_upload.maximum_size = 8388608 board = esp32dev platform = ${esp32_idf_V5.platform} platform_packages = ${esp32_idf_V5.platform_packages} -;;custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds -custom_usermods = ;; AR usermod causes bootloops on classic esp32 +custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds build_unflags = ${esp32_idf_V5.build_unflags} build_flags = ${common.build_flags} ${esp32_idf_V5.build_flags} -D WLED_RELEASE_NAME=\"ESP32_16M\" #-D WLED_DISABLE_BROWNOUT_DET -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 @@ -868,8 +859,7 @@ board = esp32-poe platform = ${esp32_idf_V5.platform} platform_packages = ${esp32_idf_V5.platform_packages} upload_speed = 921600 -;;custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds -custom_usermods = ;; AR usermod causes bootloops on classic esp32 +custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds build_unflags = ${esp32_idf_V5.build_unflags} build_flags = ${common.build_flags} ${esp32_idf_V5.build_flags} -D WLED_RELEASE_NAME=\"ESP32_Ethernet\" -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 -D SR_DMTYPE=-1 -D AUDIOPIN=-1 -D I2S_SDPIN=-1 -D I2S_WSPIN=-1 -D I2S_CKPIN=-1 -D MCLK_PIN=-1 ;; force AR to not allocate any PINs at startup @@ -888,8 +878,7 @@ board = ttgo-t7-v14-mini32 board_build.f_flash = 80000000L board_build.flash_mode = qio board_build.partitions = ${esp32.extended_partitions} -;;custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds -custom_usermods = ;; AR usermod causes bootloops on classic esp32 +custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds build_unflags = ${esp32_idf_V5.build_unflags} build_flags = ${common.build_flags} ${esp32_idf_V5.build_flags} -D WLED_RELEASE_NAME=\"ESP32_WROVER\" -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 diff --git a/usermods/audioreactive/audio_reactive.cpp b/usermods/audioreactive/audio_reactive.cpp index 914cc7e8cf..c257b4e59c 100644 --- a/usermods/audioreactive/audio_reactive.cpp +++ b/usermods/audioreactive/audio_reactive.cpp @@ -4,11 +4,48 @@ #ifdef ARDUINO_ARCH_ESP32 #include -#if defined(CONFIG_IDF_TARGET_ESP32) && (ESP_IDF_VERSION_MAJOR < 5) -#include // legacy ADC driver causes bootloops in V5 +#include #endif +// AI: below section was generated by an AI +#if defined(CONFIG_IDF_TARGET_ESP32) && (ESP_IDF_VERSION_MAJOR >= 5) +/* + * Bootloop workaround for classic ESP32 on esp-idf 5.x (WLED V5): + * AR still uses the legacy I2S driver (driver/i2s.h) which pulls in the legacy ADC driver. + * This causes a "abort" on boot as IDF V5 does not allow the legacy ADC driver to be used + * if any other function (i.e. Arduino's analogRead()) pulls in the new driver. + * + * The no-op stubs below satisfy exactly the symbols i2s_legacy.c.obj needs, so the real + * adc_i2s_deprecated.c.obj / adc_legacy.c.obj are never linked. This is safe because on IDF >= 5 + * the analog built-in-ADC mic class (I2SAdcSource) is compiled out anyway (see audio_source.h), + * so these paths are never executed; digital I2S/PDM mics don't use them. + * Delete this block once the usermod is ported to the new I2S driver (driver/i2s_std.h). + */ +#include + +// Stub: would configure the (removed) I2S built-in ADC mode. Never called for digital mics. +extern "C" esp_err_t adc_i2s_mode_init(int adcUnit, int channel) { + (void)adcUnit; (void)channel; + return ESP_ERR_NOT_SUPPORTED; +} + +// Stub: would select the ADC data source for I2S DMA. Never called for digital mics. +extern "C" esp_err_t adc_set_i2s_data_source(int source) { + (void)source; + return ESP_ERR_NOT_SUPPORTED; +} + +// Stub: would acquire ADC1 for DMA (built-in ADC mode only). Report failure if ever called. +extern "C" esp_err_t adc1_dma_mode_acquire(void) { + return ESP_ERR_NOT_SUPPORTED; +} + +// Stub: would release the legacy ADC1 lock. No lock is ever taken, so nothing to do. +extern "C" esp_err_t adc1_lock_release(void) { + return ESP_OK; +} #endif +// AI: end #if defined(ARDUINO_ARCH_ESP32) && (defined(WLED_DEBUG) || defined(SR_DEBUG)) #include diff --git a/wled00/util.cpp b/wled00/util.cpp index d31e9e2bec..8ac46c77df 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -1327,39 +1327,171 @@ String computeSHA1(const String& input) { } #ifdef ESP32 -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) -#include "esp_adc_cal.h" // ToDO: deprecated API -//#include "esp_adc/adc_cali.h" // new API -//#include "esp_adc/adc_cali_scheme.h" // new API -#else +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) #include "esp_adc_cal.h" #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,4,7) // backwards compatibility patch #define ADC_ATTEN_DB_12 ADC_ATTEN_DB_11 #endif +#else // IDF V5 +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 +// Functions to reproduce the legacy ADC calibration factors to be used as "salt" for device ID +// AI: below section was (mostly) generated by an AI, tested and confirmed working as intended on all targets +#if CONFIG_IDF_TARGET_ESP32 +// ESP32 legacy ADC calibration factors Line Fitting + LUT (LUT is consolidated into a single value) +#include "soc/soc.h" +#include "soc/efuse_reg.h" +#define VREF_REG EFUSE_BLK0_RDATA4_REG +#define VREF_MASK 0x1F +#define VREF_STEP_SIZE 7 +#define VREF_OFFSET 1100 +#define TP_REG EFUSE_BLK3_RDATA3_REG +#define BLK3_RESERVED_REG EFUSE_BLK0_RDATA3_REG +#define TP_LOW1_OFFSET 278 +#define TP_LOW_MASK 0x7F +#define TP_LOW_VOLTAGE 150 +#define TP_HIGH1_OFFSET 3265 +#define TP_HIGH_MASK 0x1FF +#define TP_HIGH_VOLTAGE 850 +#define TP_STEP_SIZE 4 +#define ADC_12_BIT_RES 4096 +// Hardcoded legacy constants for ADC_ATTEN_DB_12 (Index 3) +constexpr uint32_t TP_ATTEN_SCALE_DB12 = 224310; +constexpr uint32_t TP_ATTEN_OFFSET_DB12 = 54; +constexpr uint32_t VREF_ATTEN_SCALE_DB12 = 196602; +constexpr uint32_t VREF_ATTEN_OFFSET_DB12 = 142; +// Compile-time pre-calculated XOR sums of the first 8 elements of the legacy 20-point LUTs (replaces the V4 XOR for-loop) +constexpr uint32_t LUT_ADC1_LOW_XOR_8 = 2240 ^ 2297 ^ 2352 ^ 2405 ^ 2457 ^ 2512 ^ 2564 ^ 2616; +constexpr uint32_t LUT_ADC1_HIGH_XOR_8 = 2667 ^ 2706 ^ 2745 ^ 2780 ^ 2813 ^ 2844 ^ 2873 ^ 2901; + +static inline int32_t decodeBits(uint32_t bits, uint32_t mask, bool twosComplement) { + if (bits & (~(mask >> 1) & mask)) { + return twosComplement ? -(int32_t)(((~bits) + 1) & (mask >> 1)) : -(int32_t)(bits & (mask >> 1)); + } + return (int32_t)(bits & (mask >> 1)); +} + +static void getLegacyAdcCoeffs(uint32_t *coeff_a, uint32_t *coeff_b) { + bool tpBurned = (REG_GET_FIELD(BLK3_RESERVED_REG, EFUSE_RD_BLK3_PART_RESERVE) != 0) && + (REG_GET_FIELD(TP_REG, EFUSE_RD_ADC1_TP_LOW) != 0) && + (REG_GET_FIELD(TP_REG, EFUSE_RD_ADC1_TP_HIGH) != 0); + + if (tpBurned) { + uint32_t lowBits = REG_GET_FIELD(TP_REG, EFUSE_RD_ADC1_TP_LOW); + uint32_t highBits = REG_GET_FIELD(TP_REG, EFUSE_RD_ADC1_TP_HIGH); + + uint32_t low = TP_LOW1_OFFSET + decodeBits(lowBits, TP_LOW_MASK, true) * TP_STEP_SIZE; + uint32_t high = TP_HIGH1_OFFSET + decodeBits(highBits, TP_HIGH_MASK, true) * TP_STEP_SIZE; + + uint32_t delta_x = high - low; + uint32_t delta_v = TP_HIGH_VOLTAGE - TP_LOW_VOLTAGE; + + *coeff_a = (delta_v * TP_ATTEN_SCALE_DB12 + delta_x / 2) / delta_x; + *coeff_b = TP_HIGH_VOLTAGE - ((delta_v * high + delta_x / 2) / delta_x) + TP_ATTEN_OFFSET_DB12; + } else { + bool vrefBurned = (REG_GET_FIELD(VREF_REG, EFUSE_RD_ADC_VREF) != 0); + uint32_t vrefBits = REG_GET_FIELD(VREF_REG, EFUSE_ADC_VREF); + uint32_t vref = vrefBurned ? (VREF_OFFSET + decodeBits(vrefBits, VREF_MASK, false) * VREF_STEP_SIZE) : 1100; + + *coeff_a = (vref * VREF_ATTEN_SCALE_DB12) / ADC_12_BIT_RES; + *coeff_b = VREF_ATTEN_OFFSET_DB12; + } +} #endif + +#if CONFIG_IDF_TARGET_ESP32S2 +#include "hal/adc_types.h" +#include "esp_efuse_rtc_table.h" +// note: this is confirmed working on v2 calib S2 chips, had no v1 S2 to test +static void getLegacyAdcCoeffs(uint32_t *coeff_a, uint32_t *coeff_b) { + *coeff_a = 0; + *coeff_b = 0; + static const uint32_t v_high = 2000; // Hardcoded legacy constant for ADC_ATTEN_DB_12 + const uint32_t v_low = 250; + const int atten = ADC_ATTEN_DB_12; // 3 + + int version = esp_efuse_rtc_table_read_calib_version(); + if (version == 1) { + int tag_l = esp_efuse_rtc_table_get_tag(1, ADC_UNIT_1, atten, RTCCALIB_V1_PARAM_VLOW); + int tag_h = esp_efuse_rtc_table_get_tag(1, ADC_UNIT_1, atten, RTCCALIB_V1_PARAM_VHIGH); + uint32_t low = (uint32_t)esp_efuse_rtc_table_get_parsed_efuse_value(tag_l, false); + uint32_t high = (uint32_t)esp_efuse_rtc_table_get_parsed_efuse_value(tag_h, false); + uint32_t delta = high - low; + if (delta == 0) return; + // identical operand types/order to v4.4 characterize_using_two_point() + *coeff_a = 65536u * (v_high - v_low) / delta; + *coeff_b = 1024u * (v_low * high - v_high * low) / delta; // non-zero on v1 chips! + } else if (version == 2) { + int tag = esp_efuse_rtc_table_get_tag(2, ADC_UNIT_1, atten, RTCCALIB_V2_PARAM_VHIGH); + uint32_t high = (uint32_t)esp_efuse_rtc_table_get_parsed_efuse_value(tag, false); + if (high != 0) *coeff_a = 65536u * v_high / high; + // v4.4: coeff_b = 0 + } +} +#endif +// ESP32-S3 & ESP32-C3 coefficient extraction matching IDF V4 +#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 +#include "hal/adc_types.h" +#include "esp_efuse_rtc_calib.h" + +static void getLegacyAdcCoeffs(uint32_t *coeff_a, uint32_t *coeff_b) { + *coeff_a = 0; + *coeff_b = 0; // v4.4: coeff_b = 0 on both targets + int ver = esp_efuse_rtc_calib_get_ver(); + if (ver != 1) return; // v4.4: ESP_ADC_CAL_VAL_NOT_SUPPORTED + + uint32_t digi = 0, voltage = 0; + // C3 ignores the unit arg (V1 calib exists only for ADC1); S3 uses it + esp_err_t ret = esp_efuse_rtc_calib_get_cal_voltage(ver, ADC_UNIT_1, ADC_ATTEN_DB_12, &digi, &voltage); + if (ret != ESP_OK || digi == 0) return; + + #if CONFIG_IDF_TARGET_ESP32S3 + *coeff_a = 1000000u * voltage / digi; // v4.4 S3: coeff_a_scaling = 1000000 + #else + *coeff_a = 65536u * voltage / digi; // v4.4 C3: coeff_a_scaling = 65536 + #endif +} +#endif // S3 or C3 +#else // not ESP32, S2, C3 or S3 and V5: read data from efuse BLOCK2 as salt +#include "esp_efuse.h" +uint32_t get_calib_entropy(void) { + // Bytes 0-15 are OPTIONAL_UNIQUE_ID, which may be all-zero (undocumented feature) + // Bytes 16+ fall in the calibration-trim region (temp calib, ADC init codes, etc.), + // which IS reliably burned on every chip and varies die-to-die due to manufacturing variance. + uint8_t sys_data[32] = {0}; + esp_efuse_read_block(EFUSE_BLK2, sys_data, 0, sizeof(sys_data) * 8); // size is in bits + uint32_t entropy = 0; + for (int i = 16; i < 32; i++) entropy = entropy * 16777619u ^ sys_data[i]; + return entropy; +} +#endif // ESP32, S2, C3, S3 +#endif // IDF V5 +// AI: end + String generateDeviceFingerprint() { uint32_t fp[2] = {0, 0}; // create 64 bit fingerprint esp_chip_info_t chip_info; esp_chip_info(&chip_info); esp_efuse_mac_get_default((uint8_t*)fp); fp[1] ^= ESP.getFlashChipSize(); -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) fp[0] ^= chip_info.full_revision | (chip_info.model << 16); -#else + #else fp[0] ^= chip_info.revision | (chip_info.model << 16); -#endif + #endif -#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 - // mix in ADC calibration data - legacy adc calibration API is not supported on new MCUs (-C5, -C6, -C61, -P4) + #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + // mix in ADC calibration data - legacy adc calibration API is not supported on new MCUs (-C5, -C6, -C61, -P4) esp_adc_cal_characteristics_t ch; #if (SOC_ADC_MAX_BITWIDTH == 13) || (CONFIG_SOC_ADC_RTC_MAX_BITWIDTH == 13) // S2 has 13 bit ADC constexpr auto myBIT_WIDTH = ADC_WIDTH_BIT_13; #else constexpr auto myBIT_WIDTH = ADC_WIDTH_BIT_12; #endif - esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_12, myBIT_WIDTH, 1100, &ch); + esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_12, myBIT_WIDTH, 1100, &ch); // note: deprecated in IDF V5 hence the complicated workaround fp[0] ^= ch.coeff_a; fp[1] ^= ch.coeff_b; + // note: these curves are LUT, hardcoded and only on ESP32 if (ch.low_curve) { for (int i = 0; i < 8; i++) { fp[0] ^= ch.low_curve[i]; @@ -1370,11 +1502,24 @@ String generateDeviceFingerprint() { fp[1] ^= ch.high_curve[i]; } } -#else - // some extra salt, instead of ADC calibration - fp[0] ^= chip_info.features | chip_info.cores << 16; + #else // V5 + uint32_t coeff_a = 0, coeff_b = 0; + // get equivalent to V4 adc coefficients without using the deprecated esp_adc_cal_characterize() + getLegacyAdcCoeffs(&coeff_a, &coeff_b); // use ADC calibration coefficients + fp[0] ^= coeff_a; + fp[1] ^= coeff_b; // note: is zero on S3 and C3 as well as S2 "v2" + #if CONFIG_IDF_TARGET_ESP32 + // Mix in the pre-calculated LUT XOR sums to match pre 17.0 fingerprint + fp[0] ^= LUT_ADC1_LOW_XOR_8; + fp[1] ^= LUT_ADC1_HIGH_XOR_8; + #endif // ESP32 + #endif // V4 or V5 + + #else // ESP32 family but not classic ESP32, S2, S3 or C3 + fp[0] ^= get_calib_entropy(); // add BLOCK2 efuse values containing calibration data + fp[0] ^= chip_info.features | chip_info.cores << 16; // some extra salt fp[1] ^= ESP.getFlashSourceFrequencyMHz() | ESP.getFlashClockDivider() << 8 ; -#endif + #endif char fp_string[17]; // 16 hex chars + null terminator sprintf(fp_string, "%08X%08X", fp[1], fp[0]); return String(fp_string);