ESP32S3基于espidf使能外部PSRAM配置

ESP32S3基于espidf使能外部PSRAM配置


  • 📍官方文档介绍:https://docs.espressif.com/projects/esp-idf/zh_CN/v5.0/esp32/migration-guides/release-5.x/system.html?highlight=psram
  • 所使用的型号:ESP32-S3-WROOM-1,QSPI FLASH + PSRAM:N16R8
    在这里插入图片描述
  • 🔧在VS Code中配置
    在这里插入图片描述
  • 🌿ESP PSRAM选项

在这里插入图片描述

  • 使能ESP PSRAM选项:
    在这里插入图片描述

📘SPI RAM(PSRAM)存储方法,可选配置项

  • Integrate RAM into memory map:适合需要直接控制内存布局和访问的场景。

将PSRAM集成到ESP32-S3的内存映射中。这意味着PSRAM将被视为系统内存的一部分,可以直接通过内存地址访问。

  • Make RAM allocatable using heap_caps_malloc(…, MALLOC_CAP_SPIRAM):适合需要明确从PSRAM中分配内存的场景。

允许使用 heap_caps_malloc 等函数从PSRAM中分配内存。MALLOC_CAP_SPIRAM 是一个标志,用于指定从PSRAM中分配内存。

  • Make RAM allocatable using malloc as well:适合希望简化内存分配流程的场景。

允许使用标准的 malloc 函数从PSRAM中分配内存。启用此选项后,malloc 会自动从PSRAM中分配内存(如果可用)。

📑集成片外 RAM 到 ESP32-S3 内存映射
  • 在 CONFIG_SPIRAM_USE 中选择 Integrate RAM into memory map 选项,以集成片外 RAM 到 ESP32-S3 内存映射。
    在这里插入图片描述
  • 这种方式的优点是访问速度快,但需要手动管理内存分配和释放。
    适用场景:适用于需要直接控制内存布局和访问的场景,例如高性能计算或需要直接操作内存的应用。
  • 调用方式
#include <stdio.h>
#include <stdlib.h>

#define PSRAM_START_ADDR 0x3F800000  // PSRAM 起始地址
#define PSRAM_SIZE (8 * 1024 * 1024) // PSRAM 大小(假设为 8MB)

void app_main(void)
{
    // 定义一个指向 PSRAM 起始地址的指针
    uint8_t *psram_ptr = (uint8_t *)PSRAM_START_ADDR;

    // 向 PSRAM 写入数据
    for (int i = 0; i < 1024; i++) {
        psram_ptr[i] = i % 256;  // 写入 0~255 的循环数据
    }

    // 从 PSRAM 读取数据并打印
    printf("PSRAM data:\n");
    for (int i = 0; i < 1024; i++) {
        printf("%02X ", psram_ptr[i]);
        if ((i + 1) % 16 == 0) {
            printf("\n");
        }
    }
}
添加片外 RAM 到堆内存分配器

在 CONFIG_SPIRAM_USE 中选择 Make RAM allocatable using heap_caps_malloc(..., MALLOC_CAP_SPIRAM) 选项。
在这里插入图片描述

启用上述选项后,片外 RAM 被映射到数据虚拟地址空间,并将这个区域添加到携带 MALLOC_CAP_SPIRAM 标志的 堆内存分配器。

适用场景:适用于需要明确从PSRAM中分配内存的场景,例如需要大块内存但不想占用内部SRAM的应用。

  • 调用方式
void *psram_mem = heap_caps_malloc(1024, MALLOC_CAP_SPIRAM);
if (psram_mem == NULL) {
    // 处理分配失败
}
调用 malloc() 分配片外 RAM
  • 在 CONFIG_SPIRAM_USE 中选择 Make RAM allocatable using malloc() as well 选项,该选项为默认选项。
    在这里插入图片描述

启用此选项后,片外存储器将被添加到内存分配程序(与上一选项相同),同时也将被添加到由标准 malloc() 函数返回的 RAM 中。应用程序因此可以使用片外 RAM,无需重写代码就能使用 heap_caps_malloc(..., MALLOC_CAP_SPIRAM)

这种方式简化了内存分配的过程,适合不需要关心内存来源的场景。
适用场景:适用于希望简化内存分配流程的场景,例如一般的应用程序开发。

  • 调用方法
void *mem = malloc(1024);
if (mem == NULL) {
    // 处理分配失败
}

📄使能外部PSRAM配置代码

  • 使用的是smartconfig例程作为模板工程创建。
/* Esptouch example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_eap_client.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_smartconfig.h"
#include "esp_mac.h"

#include "driver/gpio.h"

#include "esp_sntp.h"
#include "esp_psram.h"

#define BLINK_GPIO GPIO_NUM_4

/* FreeRTOS event group to signal when we are connected & ready to make a request */
static EventGroupHandle_t s_wifi_event_group;

static uint8_t s_led_state = 0;
/* The event group allows multiple bits for each event,
   but we only care about one event - are we connected
   to the AP with an IP? */
static const int CONNECTED_BIT = BIT0;
static const int ESPTOUCH_DONE_BIT = BIT1;
static const char *TAG = "smartconfig_example";

static void smartconfig_example_task(void *parm);

static void initialize_sntp(void);

static void event_handler(void *arg, esp_event_base_t event_base,
                          int32_t event_id, void *event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
    {
        xTaskCreate(smartconfig_example_task, "smartconfig_example_task", 4096, NULL, 3, NULL);
    }
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
    {
        esp_wifi_connect();
        xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT);
    }
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
    {
        ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
        ESP_LOGI("WIFI", "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
        ESP_LOGI(TAG, "Wi-Fi connected, initializing SNTP...");
        initialize_sntp(); // 初始化SNTP
        xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT);
    }
    else if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE)
    {
        ESP_LOGI(TAG, "Scan done");
    }
    else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL)
    {
        ESP_LOGI(TAG, "Found channel");
    }
    else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD)
    {
        ESP_LOGI(TAG, "Got SSID and password");

        smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t *)event_data;
        wifi_config_t wifi_config;
        uint8_t ssid[33] = {0};
        uint8_t password[65] = {0};
        uint8_t rvd_data[33] = {0};

        bzero(&wifi_config, sizeof(wifi_config_t));
        memcpy(wifi_config.sta.ssid, evt->ssid, sizeof(wifi_config.sta.ssid));
        memcpy(wifi_config.sta.password, evt->password, sizeof(wifi_config.sta.password));

#ifdef CONFIG_SET_MAC_ADDRESS_OF_TARGET_AP
        wifi_config.sta.bssid_set = evt->bssid_set;
        if (wifi_config.sta.bssid_set == true)
        {
            ESP_LOGI(TAG, "Set MAC address of target AP: " MACSTR " ", MAC2STR(evt->bssid));
            memcpy(wifi_config.sta.bssid, evt->bssid, sizeof(wifi_config.sta.bssid));
        }
#endif

        memcpy(ssid, evt->ssid, sizeof(evt->ssid));
        memcpy(password, evt->password, sizeof(evt->password));
        ESP_LOGI(TAG, "SSID:%s", ssid);
        ESP_LOGI(TAG, "PASSWORD:%s", password);
        if (evt->type == SC_TYPE_ESPTOUCH_V2)
        {
            ESP_ERROR_CHECK(esp_smartconfig_get_rvd_data(rvd_data, sizeof(rvd_data)));
            ESP_LOGI(TAG, "RVD_DATA:");
            for (int i = 0; i < 33; i++)
            {
                printf("%02x ", rvd_data[i]);
            }
            printf("\n");
        }

        ESP_ERROR_CHECK(esp_wifi_disconnect());
        ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
        esp_wifi_connect();
    }
    else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE)
    {
        xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT);
    }
}

static void initialise_wifi(void)
{
    ESP_ERROR_CHECK(esp_netif_init());
    s_wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
    assert(sta_netif);

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_start());
}

static void smartconfig_example_task(void *parm)
{
    EventBits_t uxBits;
    wifi_config_t myconfig = {0};
    ESP_LOGI(TAG, "creat smartconfig_example_task"); // 获取wifi配置信息,如果配置过,就直接连接wifi
    esp_wifi_get_config(ESP_IF_WIFI_STA, &myconfig);
    if (strlen((char *)myconfig.sta.ssid) > 0)
    {
        ESP_LOGI(TAG, "alrealy set, SSID is :%s,start connect", myconfig.sta.ssid);
        esp_wifi_connect();
    } // 如果没有配置过,就进行配网操作
    else
    {
        ESP_LOGI(TAG, "have no set, start to config");
        ESP_ERROR_CHECK(esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS)); // 支持APP ESPTOUCH和微信AIRKISS
        smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
        ESP_ERROR_CHECK(esp_smartconfig_start(&cfg));
    }

    while (1)
    {
        uxBits = xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY);
        if (uxBits & CONNECTED_BIT)
        {
            ESP_LOGI(TAG, "WiFi Connected to ap"); // 成功连接路由器
        }
        if (uxBits & ESPTOUCH_DONE_BIT)
        {
            ESP_LOGI(TAG, "smartconfig over"); // 配网结束
            esp_smartconfig_stop();
            vTaskDelete(NULL);
        }
    }
}

static void blink_led(void)
{
    /* Set the GPIO level according to the state (LOW or HIGH)*/
    gpio_set_level(BLINK_GPIO, s_led_state);
}

static void configure_led(void)
{
    ESP_LOGI(TAG, "Example configured to blink GPIO LED!");
    gpio_reset_pin(BLINK_GPIO);
    /* Set the GPIO as a push/pull output */
    gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
}

// SNTP 初始化
static void initialize_sntp(void)
{
    ESP_LOGI(TAG, "Initializing SNTP");
    esp_netif_init(); // 初始化网络接口
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
                      // 设置时间服务器(默认使用 pool.ntp.org)
    esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
    // 添加 NTP 服务器
    esp_sntp_setservername(0, "pool.ntp.org");    // 默认服务器
    esp_sntp_setservername(1, "cn.pool.ntp.org"); // 中国 NTP 服务器
    esp_sntp_setservername(2, "ntp1.aliyun.com"); // 阿里云 NTP 服务器
    // 初始化 SNTP
    esp_sntp_init();
#else
    sntp_setoperatingmode(SNTP_OPMODE_POLL);
    sntp_setservername(0, "pool.ntp.org");
    sntp_setservername(1, "cn.pool.ntp.org");
    sntp_setservername(2, "ntp1.aliyun.com");
    sntp_init(); // 初始化 SNTP
#endif
    // 设置时区(例如:北京时间 UTC+8)
    setenv("TZ", "CST-8", 1);
    tzset();
}

// 打印当前时间
void print_current_time()
{
    time_t now;
    struct tm timeinfo;
    // char buffer[64];

    // 获取当前时间戳
    time(&now);
    // 将时间戳转换为本地时间
    localtime_r(&now, &timeinfo);

    // 格式化时间
    // 使用 strftime 格式化时间
    // strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &timeinfo);
    // ESP_LOGI(TAG, "Formatted time: %s", buffer);

    // strftime(buffer, sizeof(buffer), "%A, %B %d, %Y %I:%M:%S %p", &timeinfo);
    // ESP_LOGI(TAG, "Formatted time: %s", buffer);

    // strftime(buffer, sizeof(buffer), "Today is %A, %B %d, %Y. The time is %I:%M %p.", &timeinfo);
    // ESP_LOGI(TAG, "Formatted time: %s", buffer);
    // 使用 asctime 打印时间
    char *time_str = asctime(&timeinfo);
    if (time_str != NULL)
    {
        // 去掉 asctime 输出的换行符
        time_str[strlen(time_str) - 1] = '\0';
        ESP_LOGI(TAG, "Current time: %s", time_str);
    }
    else
    {
        ESP_LOGE(TAG, "Failed to convert time to string");
    }
}

// 打印 Wi-Fi 信息
void print_wifi_info()
{
    wifi_config_t wifi_config;
    esp_wifi_get_config(ESP_IF_WIFI_STA, &wifi_config);

    esp_netif_ip_info_t ip_info;
    esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
    if (netif && esp_netif_get_ip_info(netif, &ip_info) == ESP_OK)
    {
        ESP_LOGI(TAG, "Wi-Fi SSID: %s", (char *)wifi_config.sta.ssid);
        ESP_LOGI(TAG, "Wi-Fi Password: %s", (char *)wifi_config.sta.password);
        ESP_LOGI(TAG, "IP Address: " IPSTR, IP2STR(&ip_info.ip));
    }
    else
    {
        ESP_LOGE(TAG, "Failed to get IP information");
    }
}

void app_main(void)
{
    configure_led();
    ESP_ERROR_CHECK(nvs_flash_init());
    initialise_wifi();
    while (1)
    {
        print_current_time();
        vTaskDelay(pdMS_TO_TICKS(1000));
        print_wifi_info();
        // 检查 PSRAM 是否启用
        if (esp_psram_is_initialized())
        {
            printf("PSRAM is enabled.\n");
            // 获取 PSRAM 大小
            size_t psram_size = esp_psram_get_size();
            printf("PSRAM size: %u bytes\n", psram_size);
        }
        else
        {
            printf("PSRAM is not enabled.\n");
        }
        blink_led();
        s_led_state = !s_led_state;
    }
}

  • 串口打印信息:
    在这里插入图片描述
    从打印信息来看,8388608 bytes可以确认已经使能了外部PSRAM.
📗片外PSRAM空间申请使用

✨在申请使用片外PSRAM时,需要注意,不能将全部8MB的空间全部申请了,否则会申请失败。可以根据实际任务需求,按量申请,执行完任务后,需要调用 free();,释放PSRAM内存。

#include <stdio.h>
#include "esp_heap_caps.h"
#include "esp_psram.h"//esp_psram_is_initialized函数需要包含
void app_main() {
	// 检查 PSRAM 是否启用
        if (esp_psram_is_initialized())
        {
            printf("PSRAM is enabled.\n");
            // 获取 PSRAM 大小
            size_t psram_size = esp_psram_get_size();
            printf("PSRAM size: %u bytes\n", psram_size);
             }
        else
        {
            printf("PSRAM is not enabled.\n");
        }
    // 分配PSRAM内存
    size_t psram_size = 4 * 1024 * 1024;  // 4MB
    void *psram_memory = heap_caps_malloc(psram_size, MALLOC_CAP_SPIRAM);

    if (psram_memory == NULL) {
        printf("Failed to allocate PSRAM memory\n");
    } else {
        printf("Successfully allocated PSRAM memory at %p\n", psram_memory);
        // 使用PSRAM内存
        // ...

        // 释放PSRAM内存
        free(psram_memory);
    }
}
  • 🌿查询PSRAM空间
//需要psram配置 :Make RAM allocatable usingmalloc as well
//或者Make RAM allocatable usingheap_caps_malloc(..., MALLOC_CAP_SPIRAM)
// 获取PSRAM内存信息
    size_t psram_total = heap_caps_get_total_size(MALLOC_CAP_SPIRAM);//总空间
    size_t psram_free = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);//可以空间

    printf("Total PSRAM: %d bytes\n", psram_total);
    printf("Free PSRAM: %d bytes\n", psram_free);
### 使用PlatformIO开发ESP32-S3项目 对于未被默认支持的特定型号如ESP32-S3-N16R8,直接应用已有的板子定义可能导致无法正常工作或PSRAM未能正确初始化等问题[^1]。因此,针对这类特殊硬件配置,需手动调整`platformio.ini`文件来适配具体的开发需求。 #### 配置平台环境 在创建新项目时指定目标框架为espidf,并选择最接近实际使用的开发板作为基础模板。之后通过编辑位于项目根目录下的`platformio.ini`文件来进行必要的自定义设置: ```ini [env:custom_esp32_s3_n16r8] platform = espressif32 board = esp32-1 ; 选取相近的基础板型 framework = espidf ; 设定使用Espressif IDF框架 upload_protocol = esp_otaf ; 设置上传协议 monitor_speed = 115200 ; 定义串口监视器波特率 build_flags = -D CONFIG_SPI RAM_SIZE=0x800000; 自定义编译选项以启用外部SPI RAM支持 boards_config_override_build_flags = SPIFFS_START=-0x290000 ; 修改分区表参数适应更大容量Flash存储 ``` 上述配置示例中特别指定了额外的构建标志用于激活外接PSRAM的支持以及调整了默认的分区布局以便充分利用较大的闪存空间。 #### 编写测试代码 编写简单的LED闪烁程序验证基本功能是否正常运作: ```cpp #include "Arduino.h" void setup() { pinMode(LED_BUILTIN, OUTPUT); } void loop() { digitalWrite(LED_BUILTIN, HIGH); // 打开 LED delay(1000); // 等待一秒 digitalWrite(LED_BUILTIN, LOW); // 关闭 LED delay(1000); // 再次等待一秒 } ``` 此段代码利用内置宏`LED_BUILTIN`控制板载指示灯实现周期性的亮灭效果,适用于大多数ESP系列模块[^5]。 #### 进行调试操作 当遇到复杂逻辑难以排查错误的情况时,可以借助于ESP32-S3自带的JTAG接口实施单步执行、查看变量值等高级调试手段。这不仅限于标准JTAG线缆连接方式,也包含了利用内部资源完成相似目的的方法[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值