乐鑫PSRAM64H搭载ESP32 硬件设计 个人设计经历分享(PSRAM不工作)
背景
在使用esp32的过程中,我希望可以在设备上运行轻量级的神经网络,或者对拍摄的图片进行本地的处理,而ESP32-S只有内部的不到300K的堆内存default memory
,这种大小的内存(RAM)显然不能完成我们的需求,所以决定添加板载的PSRAM芯片。
芯片数据
PSRAM64H datasheet
具体可以参考链接,PSRAM64与PSRAM64H 之间的区别大致只有电源电压的关系,在这里我的MCU使用为3.3V,为节省一个电压转换电路,直接使用PSRAM64H作为我的扩展内存芯片。
PSRAM64H常用封装
上面链接为立创中提供的原理图库与PCB封装,需要可以直接下载。 购买方式在此不做说明,既然你已经看到了这篇博客,说明你已经遇到了问题,或者找到了这款芯片。
原理图设计
原理图设计具体如图,在网络上没有找到任何的PSRAM硬件设计指南,此处参考了ESP32-CAM开发板的设计,经实际测试,此原理图可用。
设计中遇到的问题
在软件开发中,我使用的是PlatformIO平台,arduino框架,并非ESP-IDF
通电后,查询可用内存中,显示PSRAM部分可用为0。
- 物理连接不良
如果 ESP32 和 PSRAM 芯片之间的连线(如 SPI 总线等)存在松动、断路或者短路的情况,那么 ESP32 可能无法正确识别和访问 PSRAM,从而导致查询时显示 PSRAM 可用为 0。(一般情况不会出现此类问题) 如果出现,可能会出现报错,PSRAM ID read error: 0x00ffffff
如果出现此类情况,你大概率使用的是IDF开发,此时你需要手动设置SPI模式从4线模式,变为8线模式。
menuconfig->Component config ->ESP PSRAM->Support of external,SPI-connected RAM
menuconfig->Component config ->ESP PSRAM->SPI RAM config->
Mode (QUAD/OCT) of SPI RAM chip in use (Octal Mode PSRAM)->Octal Mode PSRAM
-
引脚配置错误
ESP32 的引脚需要正确配置才能与 PSRAM 进行通信。如果引脚功能设置错误,例如将原本用于 PSRAM 通信的引脚设置为了其他功能。 请根据你的设计 留出IO16 17引脚,正确配置PSRAM。 -
未正确初始化
此问题是常见的无法使用psram的出现情况。
spiram: SPI SRAM memory test fail.
8593/131072 writes failed,
first @ 3F800140
可能产生此类报错。
在主控模块初始化PSRAM时,会经过以下几个环节,全部通过后,会成功初始化PSRAM,可以使用。
esp_spiram_init();
esp_spiram_init_cache();
esp_spiram_test();
esp_spiram_add_to_heapalloc();
上文的报错中,产生错误为esp_spiram_test();
返回了false,导致初始化失败。
bool esp_spiram_test()
{
volatile int *spiram=(volatile int*)SOC_EXTRAM_DATA_LOW;
size_t p;
size_t s=spiram_size_usable_for_malloc();
int errct=0;
int initial_err=-1;
for (p=0; p<(s/sizeof(int)); p+=8) {
spiram[p]=p^0xAAAAAAAA;
}
for (p=0; p<(s/sizeof(int)); p+=8) {
if (spiram[p]!=(p^0xAAAAAAAA)) {
errct++;
if (errct==1) initial_err=p*4;
}
}
if (errct) {
ESP_EARLY_LOGE(TAG, "SPI SRAM memory test fail. %d/%d writes failed, first @ %X\n", errct, s/32, initial_err+SOC_EXTRAM_DATA_LOW);
return false;
} else {
ESP_EARLY_LOGI(TAG, "SPI SRAM memory test OK");
return true;
}
}
大部分版本中,esp_spiram_test()
函数内容是这样的,所以说明,此芯片在对某一地址写入时,产生了写入失败。 经过详细排查原因如下。
esp_spiram_test()失败原因
作为内存芯片PSRAM64H的时钟信号相对复杂,较易受到干扰,同时,高速读写时,四条IO线,同时按照时钟周期进行工作,但PSRAM规格说明书中,并没有提出明确的布线要求;经过测试,在布线过程中,尽可能保证PSRAM的四条IO线长度差别尽可能小,可以解决此问题;同时clk线避免与强干扰源距离太近。
重新布线打样后,问题解决。
setupserial running on core 1
setup running on core 1
PSRAM size: 8388608 bytes
Internal free size: 4453063
Deafult free size: 4453063
还可能遇到的问题
主控模块成功识别PSRAM,程序执行时,申请PSRAM重启
具体可能的表现如下
if (esp_spiram_test()){
mySerial.println("esp_spiram_test() ok");
}
//成功返回testok
heap_caps_get_free_size(MALLOC_CAP_8BIT)
heap_caps_get_free_size(MALLOC_CAP_SPIRAM)
//两个函数都能同时获取到 PSram的空间大小
//但在通过函数申请psram芯片所提供的空间时候,发生重启
(uint8_t *)heap_caps_calloc(MEMORY_SIZE, sizeof(uint8_t), MALLOC_CAP_SPIRAM);
进一步测试
#include <Arduino.h>
#include <esp_system.h>
#include <esp_spiram.h>
// 假设要分配的内存大小为1024字节
#define MEMORY_SIZE 1024
// 串口初始化(使用Serial对象)
void initSerial() {
Serial.begin(115200);
while (!Serial) {
// 等待串口连接
}
}
void setup() {
initSerial();
// 测试PSRAM
if (esp_spiram_test()) {
Serial.println("esp_spiram_test() ok");
} else {
Serial.println("PSRAM test failed!");
while (1); // 停止执行,如果PSRAM测试失败
}
delay(3000);
size_t max_free_size = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL);
printf("Maximum free heap size (internal RAM): %zu bytes\n", max_free_size);
// 获取并输出PSRAM大小
int psram_size = esp_spiram_get_size();
Serial.printf("PSRAM size: %d bytes\n", psram_size);
// 输出内部堆、SPIRAM等内存区域的空闲大小
Serial.println("Internal free size (8-bit):");
Serial.printf("%d\n", heap_caps_get_free_size(MALLOC_CAP_8BIT));
Serial.println("SPIRAM free size:");
Serial.printf("%d\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
Serial.println("Default free size:");
Serial.printf("%d\n", heap_caps_get_free_size(MALLOC_CAP_DEFAULT));
// 输出复位原因
esp_reset_reason_t reset_reason = esp_reset_reason();
Serial.println("Reset reason:");
switch (reset_reason) {
// ... (复位原因的输出代码与之前相同)
}
// 使用heap_caps_calloc从PSRAM中分配内存
uint8_t* psramMemory = (uint8_t*)heap_caps_calloc(MEMORY_SIZE, sizeof(uint8_t), MALLOC_CAP_SPIRAM);
if (psramMemory == NULL) {
Serial.println("Failed to allocate memory from SPIRAM!");
return;
}
// 初始化内存内容
for (int i = 0; i < MEMORY_SIZE; i++) {
psramMemory[i] = 'A' + (i % 26); // 循环写入A-Z
}
// 输出分配内存后的SPIRAM空闲大小
Serial.printf("SPIRAM free size after allocation: %d\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
// 通过串口输出内存内容的一部分
Serial.print("Memory content: ");
for (int i = 0; i < 64 && i < MEMORY_SIZE; i++) {
Serial.print((char)psramMemory[i]);
}
Serial.println();
// 释放内存
heap_caps_free(psramMemory); // 使用heap_caps_free来释放由heap_caps_calloc分配的内存
// 输出释放内存后的SPIRAM空闲大小
Serial.printf("SPIRAM free size after release: %d\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
}
void loop() {
}
产生如下的报错信息。
RX:esp_spiram_test() ok
RX:Maximum free heap size (internal RAM): 340996 bytes
PSRAM size: 8388608 bytes
Internal free size (8-bit):
4470923
SPIRAM free size:
4192123
Default free size:
4470923
Reset reason:
Guru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception was unhandled.
Core 1 register dump:
PC : 0x4008c0ac PS : 0x00060a33 A0 : 0x8008ddc0 A1 : 0x3ffb21a0
A2 : 0xaaaaaaaa A3 : 0xb33fffff A4 : 0x0000cdcd A5 : 0x00060a23
A6 : 0x00060a20 A7 : 0x0000abab A8 : 0x0000abab A9 : 0xffffffff
A10 : 0x00000001 A11 : 0x00000000 A12 : 0x00000000 A13 : 0x00000000
A14 : 0x6b2aaaaa A15 : 0x003fffff SAR : 0x00000004 EXCCAUSE: 0x0000001d
EXCVADDR: 0xaaaaaaaa LBEG : 0x40087695 LEND : 0x400876a5 LCOUNT : 0xffffffff
Backtrace: 0x4008c0a9:0x3ffb21a0 0x4008ddbd:0x3ffb21e0 0x4008df25:0x3ffb2200 0x4008392c:0x3ffb2220 0x40083b5b:0x3ffb2250 0x400d1585:0x3ffb2270 0x400d35b6:0x3ffb2290
ELF file SHA256: 4d5f31689b7db5de
Rebooting...
ets Jul 29 2019 12:21:46
rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:1184
load:0x40078000,len:13232
load:0x40080400,len:3028
entry 0x400805e4
Guru Meditation Error: Core 1 panic’ed (StoreProhibited)
它表明 ESP32 或 ESP32-S 系列芯片的核心 1 遇到了一个存储禁止(StoreProhibited)的异常。这通常意味着程序试图写入一个不允许写入的内存地址。
查看报错信息 0xaaaaaaaa
,是一个非法写入的地址,超出了PSRAM的地址范围。
解决方法
尝试简单的delay,避免高频写,可以有效避免此类情况。