Установка частоты CPU на ESP32

А знаете, что произойдет ,если на ESP32 (аддон версии 3.3.11) , на любой модели запустить вот такой вот скетч?

#include <Arduino.h>

void setup() {
  Serial.begin(115200);
}

void loop() {
  setCpuFrequencyMhz(123); // устанавливаем частоту 123 мегагерца. Т.к. частоту 123 МГц не поддерживает ни один из процессоров Espressif, функция всегда возвращает ошибку.
}

Довольно быстро утечет вся память, за несколько секунд. Сначала SRAM, потом, если есть - внешняя, PSRAM :slight_smile: .

@BABOS Вот вам защита от копирования/не оплаты :slightly_smiling_face:

:)) Каждый неудачный вызов сливает 256 байт. Можно даже распланировать, чтобы повисло через такое-то время.

хотел было попробовать, но. там есть проверка, но даже если её удалить, то почему-то физически не пишется туда левое число. как вам удалось? что getCpuFrequencyMhz() выдаёт?

  1. Проверка стоит , да. Но памят течет

это как? оно не должно пропускать и что попало устанавливать.

выполняет много чего на автомате ?!
setCpuFrequencyMhz(123); даже если она не выполнилась, вызывается повторная операция, и установится частота 120… вроде…

при вызове повторной операции, в новой версии это начинает съедать память ?))) в чем магия ? а главное для чего применить, можно ?

стебетесь или реально ?)))
часть кода можно разместить в psram, но уничтожит ли это часть кода такого при этом вызове ?
или только свободную оставшуюся память, если вы серьезно…

что будет если мы используем плату s2 s3 ? и как реально уничтожить код который весь будет размещен в psram ?)))

//Tools → Board: ESP32S3 Dev Module
//Tools → PSRAM: "Enabled"
//Tools → Partition Scheme: "Huge APP (3MB No OTA/1MB SPIFFS)"
//Tools → Flash Size: "16MB (128Mb)"
// Никаких специальных атрибутов НЕ СТАВИТЬ!
// Весь код автоматически загрузится в PSRAM

const int LED_PIN = 2;
volatile unsigned long counter = 0;

void heavyTask() {
    // Эта функция тоже будет в PSRAM
    for (int i = 0; i < 1000; i++) {
        counter += i;
    }
}

void setup() {
    Serial.begin(115200);
    pinMode(LED_PIN, OUTPUT);
    
    // Проверяем, где находится код
    Serial.printf("Адрес setup():   %p\n", (void*)setup);
    Serial.printf("Адрес loop():    %p\n", (void*)loop);
    Serial.printf("Адрес heavyTask(): %p\n", (void*)heavyTask);
    
    // Если адреса в диапазоне 0x3F8xxxxx - код в PSRAM
    // Если в 0x420xxxxx - код во Flash
}

void loop() {
    heavyTask();
    
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
    Serial.printf("Счетчик: %lu\n", counter);
    
    // КРИТИЧНО! Без задержки система упадет
    delay(10);  // Даем время кешу PSRAM
}

Ни мистики ни магии. Программист просто негодный.

Вот так начинается функция (она мало того что течет, в ней еще две ошибки есть, но ладно)


const char *getSupportedCpuFrequencyMhz(uint8_t xtal) {
  char *supported_frequencies = (char *)calloc(256, sizeof(char)); // <-- сожрали 256 байт, нигде не освободили
  int pos = 0;
...
...

А вот так она используется:

bool setCpuFrequencyMhz(uint32_t cpu_freq_mhz) {
...
...
  // ===== Get configuration for new frequency =====
  if (!rtc_clk_cpu_freq_mhz_to_config(cpu_freq_mhz, &conf)) {
    log_e("CPU clock could not be set to %" PRIu32 " MHz. Supported frequencies: %s", cpu_freq_mhz, getSupportedCpuFrequencyMhz(xtal));  // вот тут она и течет. Течет, даже если логи вообще выключены, потому что log_e раскрывается в log_printf() обычный.
    return false;
  }
...
...


Ну и так, по мелочи:

-- Результат calloc() проверяем на NULL?

-- Да нахрен надо. snprintf разберется :slight_smile:

-- Почему sprintf а не strcpy?

Ну, потому, что мы можем!

-- А почему просто не вернуть строковый литерал, типа return “360”?

-- Ээээ…

const char *getSupportedCpuFrequencyMhz(uint8_t xtal) {
char *supported_frequencies = (char *)calloc(256, sizeof(char));
int pos = 0;

#if TARGET_CPU_FREQ_MAX_400
#if CONFIG_IDF_TARGET_ESP32P4 && CONFIG_ESP32P4_REV_MIN_FULL < 300
pos += snprintf(supported_frequencies + pos, 256 - pos, "360");
#else
pos += snprintf(supported_frequencies + pos, 256 - pos, "400");
#endif

В этом официальном ардуиновском аддоне от Espressif много багов. Прям дофига учитывая размер когда. Куда я только не лез - втыкался в баг. Репортил. Ждал следующую версию аддона. Какие-то у них там низкокачественные кодеры сидят. Причем сам ESP-IDF написан не в пример лучше. Видимо разные группы программистов.

Оно и не пропускает. И честно об этом сообщает. Вот там-то, в сообщении и течет

Код когда пишешь для заказчика, туда можно воткнуть такоэ

// Понижаем частоту для экономии батарейки!
// Если не получилось - выставляем частоту по умолчанию (240мгц)
if (false == setCpuFrequencyMhz(66))
  setCpuFrequencyMhz(240);

И вызывать, раз в сколько то минут, например.

Безобидный код, безобидный комментарий. Никому в голову не придет, что это бонба тикающая. Когда утечет вся память - скетч или повиснет или перезагрузится. Первое даже приятнее.

Это к твоей теме, как так писать код, чтобы заказчик огреб проблем, лишившись тебя, как программиста.

ЗЫЖ я в трекер им написал, так что недолго эта бага жить будет

я сейчас на таблетках сижу,(лечусь! а то неправильно поймут)))) и сознание измененное, и отупел от них,))) выходит что чуда не будет ?))) нельзя баг использовать как фичу ?))) я бы тогда временно даже ядро скачал бы для esp32 3+ версии)))

при встрече может произойти так что у тебя выдернут из рук устройство, и скажут иди от сюда)))) не заплатив!
было бы чудесно если например кнопку нажал, и код удалился, и потом что не считывай все равно не покажет нечего…
как то вроде пытался сделать такое, но не помню получилось или нет… скорее всего не вышло, и esp с psram нет…

проверять код только если не жалко esp! а то может еще что с ней случится)))

// Никаких специальных атрибутов НЕ СТАВИТЬ!
// Весь код автоматически загрузится в PSRAM

#include <esp_private/cache_utils.h>
#include <esp_cache.h>

const int LED_PIN = 2;
volatile unsigned long counter = 0;
const unsigned long MAX_COUNTER = 500000;  // При достижении стираем функцию

// Функция, которую будем стирать по достижению счетчика
void heavyTask() {
    for (int i = 0; i < 1000; i++) {
        counter += i;
    }
}

// Функция стирания только heavyTask()
void eraseHeavyTask() {
    Serial.println("\n⚠️ СТИРАЮ ФУНКЦИЮ heavyTask()!");
    
    // 1. Получаем адрес начала функции
    uint32_t funcAddr = (uint32_t)heavyTask;
    Serial.printf("Адрес heavyTask(): 0x%08X\n", funcAddr);
    
    // 2. Определяем размер функции (приблизительно)
    // Берем 256 байт - достаточно для любой небольшой функции
    size_t funcSize = 256;
    
    // 3. Отключаем кеш перед записью в PSRAM
    uint32_t cacheReg = READ_PERI_REG(DPORT_CACHE_CONFIG_REG);
    WRITE_PERI_REG(DPORT_CACHE_CONFIG_REG, cacheReg & ~DPORT_CACHE_CONFIG_PSRAM);
    
    // 4. Записываем инструкции "останов" в начало функции
    // 0xFEEDBEEF - невалидная инструкция, вызовет панику при вызове
    uint32_t* ptr = (uint32_t*)funcAddr;
    for (size_t i = 0; i < funcSize / 4; i++) {
        ptr[i] = 0xFEEDBEEF;  // Стираем весь код функции
    }
    
    // 5. Включаем кеш обратно
    WRITE_PERI_REG(DPORT_CACHE_CONFIG_REG, cacheReg);
    
    // 6. Сбрасываем кеш инструкций
    asm volatile("ISB");
    asm volatile("DSB");
    
    Serial.println("✅ Функция heavyTask() УСПЕШНО СТЕРТА!");
    Serial.println("При следующем вызове произойдет сбой (Guru Meditation)");
}

// Функция проверки, стерта ли heavyTask()
void checkHeavyTaskStatus() {
    uint32_t funcAddr = (uint32_t)heavyTask;
    uint32_t firstWord = *((uint32_t*)funcAddr);
    
    if (firstWord == 0xFEEDBEEF || firstWord == 0) {
        Serial.println("🔴 heavyTask() СТЕРТА (0xFEEDBEEF)");
    } else {
        Serial.printf("🟢 heavyTask() ЦЕЛА: 0x%08X\n", firstWord);
    }
}

void setup() {
    Serial.begin(115200);
    pinMode(LED_PIN, OUTPUT);
    
    Serial.println("=== САМОУНИЧТОЖЕНИЕ ФУНКЦИИ ===");
    Serial.printf("Адрес setup():    %p\n", (void*)setup);
    Serial.printf("Адрес loop():     %p\n", (void*)loop);
    Serial.printf("Адрес heavyTask(): %p\n", (void*)heavyTask);
    Serial.printf("Счетчик до стирания: %lu\n", MAX_COUNTER);
    Serial.println("При достижении - heavyTask() будет стерта!");
    Serial.println("----------------------------------------");
}

void loop() {
    // ПРОВЕРКА: жива ли еще функция?
    static bool functionErased = false;
    
    // Если функция еще жива - вызываем
    if (!functionErased) {
        heavyTask();
    } else {
        // Если функция стерта - пропускаем вызов
        Serial.println("⚠️ heavyTask() стерта, пропускаем вызов");
        delay(100);
        return;
    }
    
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
    Serial.printf("Счетчик: %lu / %lu", counter, MAX_COUNTER);
    
    // Проверяем статус функции каждые 10000 итераций
    if (counter % 10000 == 0) {
        checkHeavyTaskStatus();
    }
    
    // ДОСТИГЛИ ЛИМИТА - СТИРАЕМ ФУНКЦИЮ!
    if (counter >= MAX_COUNTER) {
        Serial.println("\n🔥 ДОСТИГНУТ ЛИМИТ! СТИРАЮ heavyTask()!");
        
        // Стираем функцию
        eraseHeavyTask();
        functionErased = true;
        
        // Проверяем, что функция стерта
        checkHeavyTaskStatus();
        
        Serial.println("✅ heavyTask() больше НЕ СУЩЕСТВУЕТ!");
        Serial.println("Продолжаем работу без heavyTask()");
        Serial.println("----------------------------------------");
    }
    
    delay(10);
}

если сработает, можно наверное попытаться уже и полностью перезаписать память, а не только начало…
но скорее всего не выйдет)))

код не проверял, просто описал теоретический способ ии как можно попытаться реализовать… но поймет ли, и позволит это компилятор…)))

Не знаю, попробовал. Честно пишет что частота не алё, установлена по умолчанию 240 и работаем дальше. И работает же.

В цикле вызывай :). В моем примере - фцнкция вызывается в цикле. За каждый вызов она сжирает 256 байт. Если ее вызвать тысячу раз, она съест почти 256кб памяти.

Ага. “А вы на шкафчик залезьте” (с) анекдот

Если проблема с утечкой памяти, то причем тут эта функция? такое можно как угодно организовать. Тем более не понятно магическое число 123. Почему не 124? )))))))))

зы. да и в принципе сомнения, что на один и тот-же указатель malloc продолжает раздувать память. надо проверять.

Можно и 124. Это просто для примера значение.

Скажу так. Фреймворк ардуино в любом случае основан на родном esp-idf, т.к. всё упирается в родную freertos и обойти это нельзя. Если запустить дебаггер, можно увидеть, что до функции setup идет очень много манипуляций с настройкой этой ос, в том числе и частота. Так что простой ардуиновский вызов подобных функций надо производить с умом.