Utilisation de la PSRAM¶
Cet article explique comment utiliser la RAM externe supplémentaire (PSRAM) présente sur certaines cartes ESP32 (uPesy ESP32 Wrover DevKit, TinyPICO ) avec le langage Arduino et sur le Framework ESP-IDF.
Présentation et utilité¶
Puisque la RAM interne des microcontrôleurs est assez faible, on peut ajouter une RAM supplémentaire externe pour en avoir encore plus. Même, si l’ESP32 est un microcontrôleur qui possède beaucoup de RAM, elle peut ne pas être suffisante, surtout quand on veut manipuler de gros fichiers (JSON, HTML …)
- La PSRAM est une RAM supplémentaire externe de 4 Mo qui est présente dans certains modules ESP32:
ESP32-WROVER-B
ESP32-WROVER-I
L’ESP32 communique avec la PSRAM par le protocole SPI. C’est pourquoi elle est aussi appelée SPI RAM. Le 3ème bus SPI de l’ESP32 est utilisé pour communiquer avec la mémoire flash (qui contient le programme) et avec la PSRAM.

Aperçu interne du module ESP-WROVER-B¶
Note
Il y a une ambiguïté sur la quantité de mémoire réelle de la PSRAM. La capacité réelle de la PSRAM (sur la puce de silicium) n’est pas de 4 Mo mais de 8 Mo. Par contre, seulement 4 Mo peuvent être utilisés facilement dans nos programmes. Pour utiliser les 8 Mo de la PSRAM , c’est assez complexe. Use 8 MB of PSRAM Ceci dit 4 Mo de RAM en plus est déjà énorme (8 fois la RAM interne)!
L’intéret d’une RAM externe est de pouvoir soulager la RAM interne pour le stockage temporaire de données importantes.
- On peut par exemple:
Manipuler de gros fichiers Json
Manipuler des pages WEB entières
Faire un serveur web performant
Créer d’énormes tableaux
Manipuler les données de gros fichiers
Comment l’utiliser ?¶
Sur Arduino IDE, pour pouvoir utiliser la PSRAM, il faut sélectionner une carte compatible, comme par exemple la carte ESP32 Wrover Module qui marchera pour toutes les cartes ESP32 qui possèdent une PSRAM.

Si vous utilisez une carte présente dans la liste, comme par exemple pour la carte TinyPICO , vérifiez que la PSRAM est bien activée.

Avertissement
La PSRAM n’apparaît pas dans la table des mémoires de l’ESP32, c’est donc normal que la taille de la RAM indiquée sur Arduino IDE ou Plateformio soit toujours de 327 Ko. Il y a une séparation claire entre la RAM interne de l’ESP32 (327 Ko) et la RAM externe (4 Mo).

Pour utiliser la PSRAM, on peut soit utiliser des librairies qui prennent en charge la PSRAM ou faire soi même des allocations dynamiques pour créer des tableaux, des buffers, télécharger des fichiers HTLM, JSON …
Librairies Arduino / ESP32¶
Pour éviter de se prendre la tête avec des allocations dynamiques, certaines librairies Arduino (compatibles avec l’ESP32) prennent en charge la PSRAM. La plus connue est ArduinoJson qui permet de manipuler très facilement les fichiers JSON sur Arduino et ESP32.
ArduinoJson¶
Si vous ne connaisez pas encore ArduinoJson, regarder ce tutoriel pour installer et apprendre à utiliser la librairie.
Pour utiliser la PSRAM avec ArduinoJson, il faut créer un JsonDocument
spécifique que l’on place avant la fonction setup()
:
struct SpiRamAllocator {
void* allocate(size_t size) {
return ps_malloc(size);
}
void deallocate(void* pointer) {
free(pointer);
}
};
using SpiRamJsonDocument = BasicJsonDocument<SpiRamAllocator>;
Ensuite on utilise le SpiRamJsonDocument
comme un DynamicJsonDocument
classique :
SpiRamJsonDocument doc(100000); //100 KB here
deserializeJson(doc, input);
Voici un exemple, qui permet de télécharger un fichier JSON de 65 Ko. Le JSON contient les résultats de la recherche « ESP32 json » sur Google.
Exemple
#include <WiFi.h> #include <HTTPClient.h> #define ARDUINOJSON_DECODE_UNICODE 1 #include <ArduinoJson.h> //WiFi const char *ssid = "Nom box WiFi"; const char *password = "mdp WiFi"; const char *url = "https://raw.githubusercontent.com/uPesy/ESP32_Tutorials/master/JSON/bigJsonExample.json"; struct SpiRamAllocator { void* allocate(size_t size) { return ps_malloc(size); } void deallocate(void* pointer) { free(pointer); } }; using SpiRamJsonDocument = BasicJsonDocument<SpiRamAllocator>; void setup(){ delay(500); psramInit(); Serial.begin(115200); Serial.println((String)"Memoire disponible dans la PSRAM : " +ESP.getFreePsram()); //Se connecter au WiFi WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED){ delay(100); Serial.print("."); } Serial.print("\nConnecté au WiFi avec l'IP : "); Serial.println(WiFi.localIP()); Serial.println("Téléchargement JSON"); HTTPClient http; http.useHTTP10(true); http.begin(url); http.GET(); SpiRamJsonDocument doc(100000); //Créer un document JSON de 100 Ko DeserializationError error = deserializeJson(doc, http.getStream()); if(error){ Serial.print("deserializeJson() failed: "); Serial.println(error.c_str()); } http.end(); Serial.println((String)"Mémoire utilisée par le JsonDocument : " + doc.memoryUsage()); for (int i=0; i!=10;i++){ Serial.println("\n[+]"); Serial.println(doc["items"][i]["title"].as<String>()); Serial.println("------------------------------"); Serial.println(doc["items"][i]["snippet"].as<String>()); Serial.println("------------------------------"); Serial.println((String) "URL : " + doc["items"][i]["link"].as<String>()); } } void loop(){ }
Moniteur série:
Memoire disponible dans la PSRAM : 4194252
..............
Connecté au WiFi avec l'IP : 192.168.43.7
Téléchargement JSON
Mémoire utilisée par le JsonDocument: 62515
[+]
Installing ESP32 in Arduino IDE (Windows, Mac OS X, Linux ...
------------------------------
Installing ESP32 Add-on in Arduino IDE Windows, Mac OS X, Linux open. Enter
https://dl.espressif.com/dl/package_esp32_index.json into the “Additional Board ...
------------------------------
URL : https://randomnerdtutorials.com/installing-the-esp32-board-in-arduino-ide-windows-instructions/
[+]
ESP32: Parsing JSON – techtutorialsx
------------------------------
Apr 26, 2017 ... The objective of this post is to explain how to parse JSON messages with the
ESP32 and the ArduinoJson library. Introduction The objective of ...
------------------------------
URL : https://techtutorialsx.com/2017/04/26/esp32-parsing-json/
...
Regarder mon Instructable, qui permet de faire des recherches sur Google et d’afficher le résultat dans le moniteur série. (Marche aussi sans PSRAM)
Voir aussi
Comment utiliser la RAM externe sur l’ESP32 sur le site officiel ArduinoJson
Framework Arduino¶
Pour stocker des variables dans la PSRAM, il faut faire des allocations dynamiques avec des fonctions spécifiques pour la PSRAM de l’ESP32.
Note
Il n’est pas nécessaire d’avoir des connaissances avancées en C/C++ pour utiliser la PSRAM. La connaissances des pointeurs, des fonctions d’allocations dynamiques comme malloc()
ou calloc()
est un plus, mais les exemples proposés se suffisent à eux-mêmes pour utiliser la PSRAM dans des projets d’électronique. Pour plus d’informations sur l’allocation dynamique, regardez L’allocation dynamique sur OpenClassroom .
Les fonctions principales à utiliser sont les suivantes psramInit()
, ESP.getFreePsram()
, ps_malloc()
ou ps_calloc()
et free()
.
La fonction psramInit()
permet d’initialiser la PSRAM, la fonction ESP.getFreePsram()
renvoie la quantité de mémoire disponible dans la PSRAM.
Les 3 autres fonctions servent pour l’allocation dynamique.
Exemples¶
Cette section propose des exemples d’utilisation de la PSRAM. Ils permettent de comprendre comment utiliser les foncions citées ci-dessus. Il n’est pas nécessaire de tout comprendre pour pouvoir utiliser la PSRAM, il suffit de modifier les valeurs dans les exemples pour les utiliser dans vos projets.
Variables¶
Cet exemple montre comment créer des variables qui sont stockées dans la PSRAM:
//Créer un entier
int *var_int = (int *) ps_malloc(sizeof(int));
*var_int = 42;
//Créer un nombre flottant
float *var_float = (float *) ps_malloc(sizeof(float));
*var_float = 42.42;
Voici le sketch complet :
Exemple
void setup() { // put your setup code here, to run once: Serial.begin(115200); //Initialisation if(psramInit()){ Serial.println("\nLa PSRAM est correctement initialisée"); }else{ Serial.println("\nLa PSRAM ne fonctionne pas"); } //Créer un entier int *var_int = (int *) ps_malloc(sizeof(int)); *var_int = 42; //Créer un nombre flottant float *var_float = (float *) ps_malloc(sizeof(float)); *var_float = 42.42; Serial.println((String)"var_int = " + *var_int); Serial.print("var_float = "); Serial.println(*var_float); }Sortie du terminal :
La PSRAM est correctement initialisée var_int=42 var_float = 42.42
Tableaux¶
Pour les tableaux, la syntaxe est très similaire :
//Création d'un tableaux entiers de 1000 éléments
int n_elements = 1000;
int *tableau = (int *) ps_malloc(n_elements * sizeof(int)); //Créer un tableau d'entiers de n_elements
//On accéde aux éléments comme pour un tableau classique
tableau[0] = 42;
tableau[42] = 42;
tableau[999] = 42;
Voici le sketch complet qui permet de créer un tableau d’entiers de 1000 éléments stocké dans la PSRAM:
Exemple
int n_elements = 1000; void setup(){ Serial.begin(9600); //Initialisation if(psramInit()){ Serial.println("\nLa PSRAM est correctement initialisée"); }else{ Serial.println("La PSRAM ne fonctionne pas"); } //Création d'un tableau de n_elements int tailleInitiale = ESP.getFreePsram(); Serial.println((String)"Mémoire disponible PSRAM (octets): " + tailleInitiale); // Affiche le nombre d'octets encore disponible dans la PSRAM int *tableau = (int *) ps_malloc(n_elements * sizeof(int)); //Créer un tableau d'entiers de n_elements tableau[0] = 42; tableau[999] = 42; //On accéde aux éléments comme pour un tableau classique int taillePSRAM = ESP.getFreePsram(); Serial.println((String)"Mémoire disponible PSRAM (octets): " + taillePSRAM); // L'espace libre de la mémoire a diminuée int tailleTableau = tailleInitiale - taillePSRAM; Serial.println((String)"Taille du tableau dans la mémoire en octets : " + tailleTableau); //Suppression du tableau free(tableau); //La memoire alloué est libérée. Serial.println((String)"Mémoire disponible PSRAM (octets): " +ESP.getFreePsram()); } void loop() { }Sortie du terminal :
La PSRAM est corectement initialisée Mémoire disponible PSRAM (octets): 4194252 Mémoire disponible PSRAM (octets): 4190236 Taille du tableau dans la mémoire en octets : 4016 Mémoire disponible PSRAM (octets): 4194252
Note
La taille en octets du tableau est de 4016 pour un tableau de 1000 entiers (int). Le type int
est codé sur 4 octets. Il y a donc 4 * 1000 octets occupés par le tableau, les 16 octets restants contiennent des informations sur le bloc mémoire (taille, flags).
Pour des tableaux d’autres types de variables :
char *tableau1 = (char *) ps_malloc(n_elements * sizeof(char)); //Créer un tableau vide de n_elements caractères
float *tableau = (float *) ps_malloc(n_elements * sizeof(float)); //Créer un tableau de nombres avec virgules
On peut aussi créer des tableaux qui sont remplis de zéros avec
ps_calloc()
:
int n_elements = 20;
Serial.println((String)"Mémoire disponible PSRAM (octets): " +ESP.getFreePsram());
Serial.println("Tableau d'entiers initialisés à 0");
int *tableau = (int *) ps_calloc(n_elements, sizeof(int));
Serial.print("[tableau] : ");
for(int i=0; i!= n_elements;i++){
Serial.print((String)tableau[0] + " ");
}
Serial.println((String)"\nMémoire disponible PSRAM (octets): " +ESP.getFreePsram());
Sortie du terminal :
Mémoire disponible PSRAM (octets): 4194252
Tableau d'entiers initialisés à 0
[tableau] : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Mémoire disponible PSRAM (octets): 4194156
Voici 2 manières pour créer des tableaux à 2 dimensions :
Exemple
int n_lignes = 10; int n_colonnes = 20; void setup(){ Serial.begin(9600); //Initialisation if(psramInit()){ Serial.println("\nLa PSRAM est corectement initialisée"); }else{ Serial.println("La PSRAM ne fonctionne pas"); } //Création d'un tableau de n_elements int tailleInitiale = ESP.getFreePsram(); Serial.println((String)"Mémoire disponible PSRAM (octets): " + tailleInitiale); // Affiche le nombre d'octets encore disponible dans la PSRAM //Création d'un tableau à deux dimensions avec un tableau à une dimension int *tableau2D = (int *) ps_malloc(n_lignes * n_colonnes * sizeof(int)); // Création d'un tableau de n_lignes x n_colonnes d'entiers codés sur 4 octets //Pour accéder à la case situé à la ligne 5 à la colonne 10 int ligne_i = 5; int colonne_j = 10; tableau2D[colonne_j * n_colonnes +ligne_i] = 43; //Création d'un tableau rempli de zero à deux dimensions avec un tableau de pointeurs int **tableau2Dbis = (int **) ps_calloc(n_lignes, sizeof(int *)); for (int i =0; i!= n_colonnes;i++){ tableau2Dbis[i] = (int *) ps_calloc(n_colonnes , sizeof(int)); //Créer un tableau de pointeurs } //Pour accéder à la case situé à la ligne 5 à la colonne 10 tableau2Dbis[ligne_i][colonne_j] = 42; int taillePSRAM = ESP.getFreePsram(); Serial.println((String)"Mémoire disponible PSRAM (octets): " + taillePSRAM); // L'espace libre de la mémoire a diminuée int tailleTableau2D = tailleInitiale - taillePSRAM; Serial.println((String)"Taille du tableau dans la mémoire en octets : " + tailleTableau2D); //Suppression du tableau free(tableau2D); //La memoire alloué est libérée. free(tableau2Dbis); Serial.println((String)"Mémoire disponible PSRAM (octets): " +ESP.getFreePsram()); } void loop() { }
String / Chaine de charactères¶
Puisqu’un String est une chaine de charactères, il s’agit juste d’un tableau de type char
:
int n_elements = 20;
char *str = (char *) ps_calloc(n_elements, sizeof(char)); //Créer un tableau de n_elements caractères null ('\0')
str[0] = '4';
str[1] = '2';
Note
L’utilisation de ps_calloc()
est plus intéressante car elle permet à l’ESP32, de directement détecter la fin de la chaine de charactères.
Voici un sketch d’exemple :
void setup() {
Serial.begin(115200);
if(psramInit()){
Serial.println("\nLa PSRAM est correctement initialisée");
}else{
Serial.println("\nLa PSRAM ne fonctionne pas");
}
int n_elements = 20;
char *str = (char *) ps_calloc(n_elements, sizeof(char)); //Créer un tableau de n_elements caractères null ('\0')
for(int i = 0; i < 10;i+=3){
str[i] = '4';
str[i+1] = '2';
str[i+2] = '_';
}
Serial.println(str);
}
Sortie du terminal :
La PSRAM est correctement initialisée
42_42_42_42_
HTLM/JSON¶
Voici un exemple qui permet de télécharger n’importe quel type de fichiers textes comme des pages HTML ou des fichiers JSON (de moins de 4 Mo) et de le stocker dans la PSRAM.
#include <WiFi.h>
#include <HTTPClient.h>
#define TIMEOUT 5000
const char *ssid = "Nom Box WiFi";
const char *password = "mdp";
const char *url = "https://fr.wikipedia.org/wiki/ESP32";
void setup() {
Serial.begin(115200);
//Connect to WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED){
delay(100);
Serial.print(".");
}
Serial.print("\nConnecté au WiFi avec comme IP : ");
Serial.println(WiFi.localIP());
char *page = (char *) ps_malloc(sizeof(char));
int lengthHtml = 0;
HTTPClient http;
http.begin(url);
http.useHTTP10(true); //Permet d'essayer d'avoir un Content-Length dans le header en utilisant HTTP1
Serial.println((String) "Try GET "+ url);
int httpCode = http.GET();
if (httpCode > 0){
// Affiche le réponse le code HTTP de la requete GET
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
// Si le fichier existe (HTML, JSON, ...)
if (httpCode == HTTP_CODE_OK){ // HTTP_CODE_OK = 200
// Essaye de récupérer la taille du ficher ( -1 s'il n'y a pas de Content-Length dans l'header)
int tempLength = http.getSize();
Serial.println((String) "Content Length :" + tempLength);
//Stockage de la page dans la PSRAM
Serial.printf("Adresse mémoire de la page : %p \n", page);
// Récupère le stream TCP
WiFiClient *stream = http.getStreamPtr();
//Initialisation position du buffer dans la PSRAM
int position = 0;
uint32_t currentTime = 0;
uint8_t timeoutArboted = 1;
// Récupère toute les données du fichier
while(http.connected() && (tempLength > 0 || tempLength == -1)){
// Récupère les données disponibles (les données arrivent packets par packets)
size_t size = stream->available();
if (size){
page = (char*) ps_realloc(page, position + size + 1);
stream->readBytes(page+position, size);
position += size;
if (tempLength > 0){
tempLength -= size;
}
timeoutArboted = 1;
}else{
//Si on ne connaît pas la taille du fichier, on suppose que toutes les données sont recues avant le timeout
if(timeoutArboted){
//Lance le timer
currentTime = millis();
timeoutArboted = 0;
}else{
if(millis()-currentTime > TIMEOUT){
//On a atteind le Timeout
Serial.println("Timeout reached");
break;
}
}
}
}
*(page+position) = '\0';
lengthHtml = position;
Serial.println((String)"Downloaded " + lengthHtml + " Octets");
}
}
else{
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
//Ferme le client HTTP
http.end();
delay(1500);
Serial.println(page); // Pour afficher la page HTML entière
}
Sortie du terminal :
.............
Connecté au WiFi avec comme IP : 192.168.43.7
Try GET https://fr.wikipedia.org/wiki/ESP32
[HTTP] GET... code: 200
Content Length :66295
Adresse mémoire de la page : 0x3f800024
Downloaded 66295 Octets
<!DOCTYPE html>
<html class="client-nojs" lang="fr" dir="ltr">
<head>
<meta charset="UTF-8"/>
<title>ESP32 — Wikipédia</title>
...
...
Note
Vous pouvez remarquer que c’est beaucoup plus simple d’utiliser la librairie ArduinoJson pour télécharger des fichiers JSON.
Liste détaillée des fonctions¶
-
psramInit()
- Renvoie
true si la PSRAM s’est bien initialisée, false sinon
- Type renvoyé
booléen
Cette fonction s’assure que la PSRAM est utilisable en faisant différents tests, puis l’initialise.
-
ESP.getFreePsram()
- Renvoie
La mémoire disponible en octets de la PSRAM.
- Type renvoyé
int
Cette fonction permet de connaître la mémoire disponible dans la PSRAM. La taille maximale est de 4 194 252 octets.
-
ps_malloc(size_t tailleBloc)
- Paramètres
tailleBloc – nombre total d’octets que l’on veut réserver dans la RAM.
- Renvoie
Un pointeur du type donné en argument qui contient l’adresse du bloc mémoire.
- Type renvoyé
void *
Cette fonction permet d’allouer un bloc de mémoire dans la PSRAM.
-
ps_calloc(size_t nElements, int tailleElement)
- Paramètres
nElements – Nombre d’éléments que l’on veut réserver dans la RAM.
tailleElement – Taille en octets d’un seul élément.
- Renvoie
Un pointeur du type donné en argument qui contient l’adresse du bloc mémoire.
- Type renvoyé
void *
Cette fonction permet d’allouer un bloc de mémoire de nElements, chacun de taille tailleElement dans la PSRAM et initialise tous ces octets à la valeur de 0.
-
ps_realloc(void * adresseBloc, nouvelleTailleBloc)
- Paramètres
adresseBloc – Adresse mémoire du bloc de mémoire à réallouer.
nouvelleTailleBloc – Nouvelle taille en octets du bloc mémoire que l’on veut réserver dans la RAM.
- Renvoie
Un pointeur du type donné en argument sur la zone mémoire réallouée.
- Type renvoyé
void *
Cette fonction permet de réallouer un bloc de mémoire préalablement alloué avec ps_malloc()
ou ps_calloc()
. Si l’espace mémoire libre qui suit le bloc à réallouer est suffisamment grand pour la nouvelle taille du bloc, le bloc de mémoire d’origine est simplement agrandi. Par contre si l’espace libre n’est pas suffisant, un nouveau bloc de mémoire sera alloué, le contenu de la zone d’origine recopié dans la nouvelle zone et le bloc mémoire d’origine sera libéré automatiquement.
-
free(void *pointer)
- Paramètres
pointer (*int, *char, *double, *long...) – L’adresse du bloc de mémoire à désallouer
Cette fonction libère un bloc de mémoire alloué dynamiquement avec ps_malloc()
ou ps_calloc()
.
Framework ESP-IDF¶
Les fonctions utilisées sur le framework Arduino sont basées sur celles du Framework ESP-IDF. Ainsi les fonctions décrites ci-dessous sont utilisées implicitement sur le Framework Arduino-ESP32.
Les fonctions à utiliser sont les suivantes esp_spiram_init()
, heap_caps_get_free_size()
, heap_caps_malloc()
ou heap_caps_calloc()
et free()
Voir aussi
Pour des informations plus poussées, consulter la documentation officielle sur la PSRAM