Tasmota/tasmota/support_esp.ino

535 lines
17 KiB
Arduino
Raw Normal View History

/*
2021-02-08 10:34:29 +00:00
support_esp.ino - ESP specific code for Tasmota
2021-01-01 12:44:04 +00:00
Copyright (C) 2021 Theo Arends / Jörg Schüler-Maroldt
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
2021-02-08 10:34:29 +00:00
/*********************************************************************************************\
* ESP8266 and ESP32 specific code
*
* At the end the common Tasmota calls are provided
\*********************************************************************************************/
2020-04-19 15:58:13 +01:00
/*********************************************************************************************\
* ESP8266 Support
\*********************************************************************************************/
#ifdef ESP8266
extern "C" {
extern struct rst_info resetInfo;
}
2020-04-22 15:07:52 +01:00
uint32_t ESP_ResetInfoReason(void) {
2020-04-19 15:58:13 +01:00
return resetInfo.reason;
}
2020-04-22 15:07:52 +01:00
String ESP_getResetReason(void) {
2020-04-19 15:58:13 +01:00
return ESP.getResetReason();
}
2020-04-22 15:07:52 +01:00
uint32_t ESP_getChipId(void) {
2020-04-19 15:58:13 +01:00
return ESP.getChipId();
}
2020-04-22 15:07:52 +01:00
uint32_t ESP_getSketchSize(void) {
2020-04-19 15:58:13 +01:00
return ESP.getSketchSize();
}
2020-04-22 15:07:52 +01:00
uint32_t ESP_getFreeHeap(void) {
return ESP.getFreeHeap();
}
void ESP_Restart(void) {
2020-04-19 15:58:13 +01:00
// ESP.restart(); // This results in exception 3 on restarts on core 2.3.0
ESP.reset();
}
2020-11-22 16:35:04 +00:00
uint32_t FlashWriteStartSector(void) {
return (ESP.getSketchSize() / SPI_FLASH_SEC_SIZE) + 2; // Stay on the safe side
}
uint32_t FlashWriteMaxSector(void) {
return (((uint32_t)&_FS_start - 0x40200000) / SPI_FLASH_SEC_SIZE) - 2;
2020-11-22 16:35:04 +00:00
}
uint8_t* FlashDirectAccess(void) {
return (uint8_t*)(0x40200000 + (FlashWriteStartSector() * SPI_FLASH_SEC_SIZE));
}
2021-01-04 15:39:00 +00:00
void *special_malloc(uint32_t size) {
return malloc(size);
}
2020-04-19 15:58:13 +01:00
#endif
/*********************************************************************************************\
* ESP32 Support
\*********************************************************************************************/
#ifdef ESP32
2020-07-05 13:51:55 +01:00
// Handle 20k of NVM
#include <nvs.h>
2021-02-02 16:55:45 +00:00
2021-02-03 11:22:17 +00:00
// See libraries\ESP32\examples\ResetReason.ino
#if ESP_IDF_VERSION_MAJOR > 3 // IDF 4+
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
#include "esp32/rom/rtc.h"
#elif CONFIG_IDF_TARGET_ESP32S2 // ESP32-S2
#include "esp32s2/rom/rtc.h"
#else
#error Target CONFIG_IDF_TARGET is not supported
#endif
#else // ESP32 Before IDF 4.0
#include "rom/rtc.h"
2021-02-02 16:55:45 +00:00
#endif
2020-11-12 16:50:34 +00:00
#include <esp_phy_init.h>
2020-04-22 15:07:52 +01:00
void NvmLoad(const char *sNvsName, const char *sName, void *pSettings, unsigned nSettingsLen) {
nvs_handle handle;
2020-04-19 14:36:04 +01:00
noInterrupts();
nvs_open(sNvsName, NVS_READONLY, &handle);
size_t size = nSettingsLen;
nvs_get_blob(handle, sName, pSettings, &size);
nvs_close(handle);
interrupts();
}
2020-04-22 15:07:52 +01:00
void NvmSave(const char *sNvsName, const char *sName, const void *pSettings, unsigned nSettingsLen) {
nvs_handle handle;
2020-04-19 14:36:04 +01:00
noInterrupts();
nvs_open(sNvsName, NVS_READWRITE, &handle);
nvs_set_blob(handle, sName, pSettings, nSettingsLen);
nvs_commit(handle);
nvs_close(handle);
interrupts();
}
2020-11-12 17:37:01 +00:00
int32_t NvmErase(const char *sNvsName) {
nvs_handle handle;
noInterrupts();
2020-11-12 17:37:01 +00:00
int32_t result = nvs_open(sNvsName, NVS_READWRITE, &handle);
2020-11-12 16:38:15 +00:00
if (ESP_OK == result) { result = nvs_erase_all(handle); }
if (ESP_OK == result) { result = nvs_commit(handle); }
nvs_close(handle);
interrupts();
2020-11-12 16:38:15 +00:00
return result;
}
2020-04-22 15:07:52 +01:00
void SettingsErase(uint8_t type) {
2021-01-08 16:06:17 +00:00
// SDK and Tasmota data is held in default NVS partition
// Tasmota data is held also in file /.settings on default filesystem
2020-11-12 16:50:34 +00:00
// cal_data - SDK PHY calibration data as documented in esp_phy_init.h
// qpc - Tasmota Quick Power Cycle state
// main - Tasmota Settings data
2020-11-12 17:37:01 +00:00
int32_t r1, r2, r3;
2020-11-12 16:38:15 +00:00
switch (type) {
case 0: // Reset 2 = Erase all flash from program end to end of physical flash
case 2: // Reset 5, 6 = Erase all flash from program end to end of physical flash excluding filesystem
2020-11-12 16:38:15 +00:00
// nvs_flash_erase(); // Erase RTC, PHY, sta.mac, ap.sndchan, ap.mac, Tasmota etc.
r1 = NvmErase("qpc");
r2 = NvmErase("main");
2021-01-08 16:06:17 +00:00
r3 = TfsDeleteFile(TASM_FILE_SETTINGS);
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " Tasmota data (%d,%d,%d)"), r1, r2, r3);
2020-11-12 16:38:15 +00:00
break;
case 1: // Reset 3 = SDK parameter area
case 4: // WIFI_FORCE_RF_CAL_ERASE = SDK parameter area
2020-11-12 16:50:34 +00:00
r1 = esp_phy_erase_cal_data_in_nvs();
// r1 = NvmErase("cal_data");
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " PHY data (%d)"), r1);
2020-11-12 16:38:15 +00:00
break;
case 3: // QPC Reached = QPC, Tasmota and SDK parameter area (0x0F3xxx - 0x0FFFFF)
// nvs_flash_erase(); // Erase RTC, PHY, sta.mac, ap.sndchan, ap.mac, Tasmota etc.
r1 = NvmErase("qpc");
r2 = NvmErase("main");
// r3 = esp_phy_erase_cal_data_in_nvs();
// r3 = NvmErase("cal_data");
2021-01-23 15:26:23 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " Tasmota (%d,%d) and PHY data (%d)"), r1, r2, r3);
2021-01-08 16:06:17 +00:00
r3 = TfsDeleteFile(TASM_FILE_SETTINGS);
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " Tasmota data (%d,%d,%d)"), r1, r2, r3);
2020-11-12 16:38:15 +00:00
break;
2020-04-19 14:36:04 +01:00
}
}
2021-01-08 15:22:06 +00:00
uint32_t SettingsRead(void *data, size_t size) {
uint32_t source = 1;
2021-01-08 16:06:17 +00:00
if (!TfsLoadFile(TASM_FILE_SETTINGS, (uint8_t*)data, size)) {
2021-01-08 15:22:06 +00:00
source = 0;
NvmLoad("main", "Settings", data, size);
2021-01-08 15:22:06 +00:00
}
return source;
}
2020-04-22 15:07:52 +01:00
void SettingsWrite(const void *pSettings, unsigned nSettingsLen) {
2021-01-08 16:06:17 +00:00
TfsSaveFile(TASM_FILE_SETTINGS, (const uint8_t*)pSettings, nSettingsLen);
2020-04-19 14:36:04 +01:00
NvmSave("main", "Settings", pSettings, nSettingsLen);
}
2020-04-22 15:07:52 +01:00
void QPCRead(void *pSettings, unsigned nSettingsLen) {
2020-04-19 14:36:04 +01:00
NvmLoad("qpc", "pcreg", pSettings, nSettingsLen);
}
2020-04-22 15:07:52 +01:00
void QPCWrite(const void *pSettings, unsigned nSettingsLen) {
2020-04-19 14:36:04 +01:00
NvmSave("qpc", "pcreg", pSettings, nSettingsLen);
}
void NvsInfo(void) {
nvs_stats_t nvs_stats;
nvs_get_stats(NULL, &nvs_stats);
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_INFO, PSTR("NVS: Used %d/%d entries, NameSpaces %d"),
nvs_stats.used_entries, nvs_stats.total_entries, nvs_stats.namespace_count);
2020-07-05 14:13:57 +01:00
}
2020-11-22 16:35:04 +00:00
//
// Flash memory mapping
//
2021-02-03 11:22:17 +00:00
// See Esp.cpp
2020-11-22 16:35:04 +00:00
#include "Esp.h"
#include "esp_spi_flash.h"
#include <memory>
#include <soc/soc.h>
#include <soc/efuse_reg.h>
#include <esp_partition.h>
extern "C" {
#include "esp_ota_ops.h"
#include "esp_image_format.h"
}
2021-02-03 11:22:17 +00:00
#include "esp_system.h"
#if ESP_IDF_VERSION_MAJOR > 3 // IDF 4+
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
#include "esp32/rom/spi_flash.h"
#elif CONFIG_IDF_TARGET_ESP32S2 // ESP32-S2
#include "esp32s2/rom/spi_flash.h"
#else
#error Target CONFIG_IDF_TARGET is not supported
#endif
#else // ESP32 Before IDF 4.0
#include "rom/spi_flash.h"
#endif
2020-11-22 16:35:04 +00:00
uint32_t EspFlashBaseAddress(void) {
const esp_partition_t* partition = esp_ota_get_next_update_partition(nullptr);
if (!partition) { return 0; }
return partition->address; // For tasmota 0x00010000 or 0x00200000
}
uint32_t EspFlashBaseEndAddress(void) {
const esp_partition_t* partition = esp_ota_get_next_update_partition(nullptr);
if (!partition) { return 0; }
return partition->address + partition->size; // For tasmota 0x00200000 or 0x003F0000
}
uint8_t* EspFlashMmap(uint32_t address) {
static spi_flash_mmap_handle_t handle = 0;
if (handle) {
spi_flash_munmap(handle);
handle = 0;
}
const uint8_t* data;
int32_t err = spi_flash_mmap(address, 5 * SPI_FLASH_MMU_PAGE_SIZE, SPI_FLASH_MMAP_DATA, (const void **)&data, &handle);
/*
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Spi_flash_map %d"), err);
2020-11-22 16:35:04 +00:00
spi_flash_mmap_dump();
*/
return (uint8_t*)data;
}
/*
int32_t EspPartitionMmap(uint32_t action) {
static spi_flash_mmap_handle_t handle;
int32_t err = 0;
if (1 == action) {
const esp_partition_t *partition = esp_ota_get_running_partition();
// const esp_partition_t* partition = esp_ota_get_next_update_partition(nullptr);
if (!partition) { return 0; }
err = esp_partition_mmap(partition, 0, 4 * SPI_FLASH_MMU_PAGE_SIZE, SPI_FLASH_MMAP_DATA, (const void **)&TasmotaGlobal_mmap_data, &handle);
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Partition start 0x%08X, Partition end 0x%08X, Mmap data 0x%08X"), partition->address, partition->size, TasmotaGlobal_mmap_data);
2020-11-22 16:35:04 +00:00
} else {
spi_flash_munmap(handle);
handle = 0;
}
return err;
}
*/
//
// Crash stuff
//
2020-04-22 15:07:52 +01:00
void CrashDump(void) {
}
2020-04-22 15:07:52 +01:00
bool CrashFlag(void) {
return false;
}
2020-04-22 15:07:52 +01:00
void CrashDumpClear(void) {
}
2020-04-22 15:07:52 +01:00
void CmndCrash(void) {
/*
volatile uint32_t dummy;
dummy = *((uint32_t*) 0x00000000);
*/
}
// Do an infinite loop to trigger WDT watchdog
2020-04-22 15:07:52 +01:00
void CmndWDT(void) {
/*
volatile uint32_t dummy = 0;
while (1) {
dummy++;
}
*/
}
// This will trigger the os watch after OSWATCH_RESET_TIME (=120) seconds
2020-04-22 15:07:52 +01:00
void CmndBlockedLoop(void) {
/*
while (1) {
delay(1000);
}
*/
}
2020-04-19 14:36:04 +01:00
//
// ESP32 specific
//
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
2020-04-22 15:07:52 +01:00
void DisableBrownout(void) {
2020-04-19 14:36:04 +01:00
// https://github.com/espressif/arduino-esp32/issues/863#issuecomment-347179737
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // Disable brownout detector
}
2020-04-19 15:58:13 +01:00
//
// ESP32 Alternatives
//
2020-04-22 15:07:52 +01:00
String ESP32GetResetReason(uint32_t cpu_no) {
2021-02-02 16:55:45 +00:00
#if CONFIG_IDF_TARGET_ESP32
2020-04-19 15:58:13 +01:00
// tools\sdk\include\esp32\rom\rtc.h
switch (rtc_get_reset_reason(cpu_no)) {
case POWERON_RESET : return F("Vbat power on reset"); // 1
case SW_RESET : return F("Software reset digital core"); // 3
case OWDT_RESET : return F("Legacy watch dog reset digital core"); // 4
case DEEPSLEEP_RESET : return F("Deep Sleep reset digital core"); // 5
case SDIO_RESET : return F("Reset by SLC module, reset digital core"); // 6
case TG0WDT_SYS_RESET : return F("Timer Group0 Watch dog reset digital core"); // 7
case TG1WDT_SYS_RESET : return F("Timer Group1 Watch dog reset digital core"); // 8
case RTCWDT_SYS_RESET : return F("RTC Watch dog Reset digital core"); // 9
case INTRUSION_RESET : return F("Instrusion tested to reset CPU"); // 10
case TGWDT_CPU_RESET : return F("Time Group reset CPU"); // 11
case SW_CPU_RESET : return F("Software reset CPU"); // 12
case RTCWDT_CPU_RESET : return F("RTC Watch dog Reset CPU"); // 13
case EXT_CPU_RESET : return F("or APP CPU, reseted by PRO CPU"); // 14
case RTCWDT_BROWN_OUT_RESET : return F("Reset when the vdd voltage is not stable"); // 15
2020-11-22 16:35:04 +00:00
case RTCWDT_RTC_RESET : return F("RTC Watch dog reset digital core and rtc module"); // 16
2020-04-19 15:58:13 +01:00
}
2021-02-02 16:55:45 +00:00
#elif CONFIG_IDF_TARGET_ESP32S2
// tools\sdk\esp32\include\esp_rom\include\esp32s2\rom\rtc.h
switch (rtc_get_reset_reason(cpu_no)) {
case POWERON_RESET : return F("Vbat power on reset"); // 1
case RTC_SW_SYS_RESET : return F("Software reset digital core"); // 3
case DEEPSLEEP_RESET : return F("Deep Sleep reset digital core"); // 5
case TG0WDT_SYS_RESET : return F("Timer Group0 Watch dog reset digital core"); // 7
case TG1WDT_SYS_RESET : return F("Timer Group1 Watch dog reset digital core"); // 8
case RTCWDT_SYS_RESET : return F("RTC Watch dog Reset digital core"); // 9
case INTRUSION_RESET : return F("Instrusion tested to reset CPU"); // 10
case TG0WDT_CPU_RESET : return F("Time Group0 reset CPU"); // 11
case RTC_SW_CPU_RESET : return F("Software reset CPU"); // 12
case RTCWDT_CPU_RESET : return F("RTC Watch dog Reset CPU"); // 13
case RTCWDT_BROWN_OUT_RESET : return F("Reset when the vdd voltage is not stable"); // 15
case RTCWDT_RTC_RESET : return F("RTC Watch dog reset digital core and rtc module"); // 16
case TG1WDT_CPU_RESET : return F("Time Group1 reset CPU"); // 17
case SUPER_WDT_RESET : return F("Super watchdog reset digital core and rtc module"); // 18
case GLITCH_RTC_RESET : return F("Glitch reset digital core and rtc module"); // 19
}
#endif
return F("No meaning"); // 0 and undefined
2020-04-19 15:58:13 +01:00
}
2020-04-22 15:07:52 +01:00
String ESP_getResetReason(void) {
2020-04-19 15:58:13 +01:00
return ESP32GetResetReason(0); // CPU 0
}
2020-04-22 15:07:52 +01:00
uint32_t ESP_ResetInfoReason(void) {
2020-04-19 15:58:13 +01:00
RESET_REASON reason = rtc_get_reset_reason(0);
2021-02-02 16:55:45 +00:00
#if CONFIG_IDF_TARGET_ESP32
2020-04-19 15:58:13 +01:00
if (POWERON_RESET == reason) { return REASON_DEFAULT_RST; }
if (SW_CPU_RESET == reason) { return REASON_SOFT_RESTART; }
if (DEEPSLEEP_RESET == reason) { return REASON_DEEP_SLEEP_AWAKE; }
if (SW_RESET == reason) { return REASON_EXT_SYS_RST; }
2021-02-02 16:55:45 +00:00
#elif CONFIG_IDF_TARGET_ESP32S2
if (POWERON_RESET == reason) { return REASON_DEFAULT_RST; }
if (RTC_SW_CPU_RESET == reason) { return REASON_SOFT_RESTART; }
if (DEEPSLEEP_RESET == reason) { return REASON_DEEP_SLEEP_AWAKE; }
if (RTC_SW_SYS_RESET == reason) { return REASON_EXT_SYS_RST; }
#endif
return -1; //no "official error code", but should work with the current code base
2020-04-19 15:58:13 +01:00
}
2020-04-22 15:07:52 +01:00
uint32_t ESP_getChipId(void) {
2020-04-19 15:58:13 +01:00
uint32_t id = 0;
for (uint32_t i = 0; i < 17; i = i +8) {
id |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
}
return id;
}
2020-04-22 15:07:52 +01:00
uint32_t ESP_getSketchSize(void) {
2020-04-19 15:58:13 +01:00
static uint32_t sketchsize = 0;
if (!sketchsize) {
sketchsize = ESP.getSketchSize(); // This takes almost 2 seconds on an ESP32
}
return sketchsize;
}
2020-04-22 15:07:52 +01:00
uint32_t ESP_getFreeHeap(void) {
2021-02-03 19:35:01 +00:00
return ESP.getFreeHeap();
2020-04-22 15:07:52 +01:00
}
uint32_t ESP_getMaxAllocHeap(void) {
2020-08-12 11:11:47 +01:00
// largest block of heap that can be allocated at once
uint32_t free_block_size = ESP.getMaxAllocHeap();
if (free_block_size > 100) { free_block_size -= 100; }
return free_block_size;
}
2020-04-22 15:07:52 +01:00
void ESP_Restart(void) {
2020-04-19 15:58:13 +01:00
ESP.restart();
}
2020-11-22 16:35:04 +00:00
uint32_t FlashWriteStartSector(void) {
// Needs to be on SPI_FLASH_MMU_PAGE_SIZE (= 0x10000) alignment for mmap usage
uint32_t aligned_address = ((EspFlashBaseAddress() + (2 * SPI_FLASH_MMU_PAGE_SIZE)) / SPI_FLASH_MMU_PAGE_SIZE) * SPI_FLASH_MMU_PAGE_SIZE;
return aligned_address / SPI_FLASH_SEC_SIZE;
}
uint32_t FlashWriteMaxSector(void) {
// Needs to be on SPI_FLASH_MMU_PAGE_SIZE (= 0x10000) alignment for mmap usage
uint32_t aligned_end_address = (EspFlashBaseEndAddress() / SPI_FLASH_MMU_PAGE_SIZE) * SPI_FLASH_MMU_PAGE_SIZE;
return aligned_end_address / SPI_FLASH_SEC_SIZE;
}
uint8_t* FlashDirectAccess(void) {
uint32_t address = FlashWriteStartSector() * SPI_FLASH_SEC_SIZE;
uint8_t* data = EspFlashMmap(address);
/*
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Flash start address 0x%08X, Mmap address 0x%08X"), address, data);
2020-11-22 16:35:04 +00:00
uint8_t buf[32];
memcpy(buf, data, sizeof(buf));
AddLogBuffer(LOG_LEVEL_DEBUG, (uint8_t*)&buf, 32);
*/
return data;
}
2021-01-01 16:04:36 +00:00
2021-01-04 15:39:00 +00:00
void *special_malloc(uint32_t size) {
if (psramFound()) {
return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
} else {
return malloc(size);
}
}
#endif // ESP32
2021-02-08 10:34:29 +00:00
/*********************************************************************************************\
* ESP Support
\*********************************************************************************************/
String GetDeviceHardware(void) {
char buff[10];
#ifdef ESP8266
// esptool.py get_efuses
uint32_t efuse1 = *(uint32_t*)(0x3FF00050);
uint32_t efuse2 = *(uint32_t*)(0x3FF00054);
// uint32_t efuse3 = *(uint32_t*)(0x3FF00058);
// uint32_t efuse4 = *(uint32_t*)(0x3FF0005C);
bool is_8285 = ( (efuse1 & (1 << 4)) || (efuse2 & (1 << 16)) );
if (is_8285 && (ESP.getFlashChipRealSize() > 1048576)) {
is_8285 = false; // ESP8285 can only have 1M flash
}
if (is_8285) {
strcpy_P(buff, PSTR("ESP8285"));
} else {
strcpy_P(buff, PSTR("ESP8266EX"));
}
#endif // ESP8266
#ifdef ESP32
#if CONFIG_IDF_TARGET_ESP32S2 // ESP32-S2
strcpy_P(buff, PSTR("ESP32-S2"));
#else
strcpy_P(buff, PSTR("ESP32"));
#endif // CONFIG_IDF_TARGET_ESP32S2
#endif // ESP32
return String(buff);
}
uint32_t ESP_getFreeHeap1024(void) {
return ESP_getFreeHeap() / 1024;
}
/*
float ESP_getFreeHeap1024(void) {
return ((float)ESP_getFreeHeap()) / 1024;
}
*/
/*********************************************************************************************\
* High entropy hardware random generator
* Thanks to DigitalAlchemist
\*********************************************************************************************/
// Based on code from https://raw.githubusercontent.com/espressif/esp-idf/master/components/esp32/hw_random.c
uint32_t HwRandom(void) {
#if ESP8266
// https://web.archive.org/web/20160922031242/http://esp8266-re.foogod.com/wiki/Random_Number_Generator
#define _RAND_ADDR 0x3FF20E44UL
#endif // ESP8266
#ifdef ESP32
#define _RAND_ADDR 0x3FF75144UL
#endif // ESP32
static uint32_t last_ccount = 0;
uint32_t ccount;
uint32_t result = 0;
do {
ccount = ESP.getCycleCount();
result ^= *(volatile uint32_t *)_RAND_ADDR;
} while (ccount - last_ccount < 64);
last_ccount = ccount;
return result ^ *(volatile uint32_t *)_RAND_ADDR;
#undef _RAND_ADDR
}