Utilisation de la PSRAM

Cet article explique comment l’utiliser la RAM externe supplémentaire de la carte uPesy ESP32 Wrover Devkit sur le Framework 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. 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.

../_images/psram.jpg

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 le programme. 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 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 ?

Avertissement

Sur l’IDE Arduino, pour pouvoir utiliser la PSRAM, il faut sélectionner une carte compatible, comme par exemple la carte ESP32 Wrover Module.

Par défaut, 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 320 Ko. Il y a donc par défaut, une séparation claire entre la RAM interne de l’ESP32 (320 Ko) et la RAM externe (4 Mo).

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 . Certaines librairies prennent en charge la PSRAM et permettent ainsi de l’utiliser facilement.

On peut donc 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 …

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.

#include <WiFi.h>
#include <HTTPClient.h>
#define ARDUINOJSON_DECODE_UNICODE 1
#include <ArduinoJson.h>

//WiFi
const char *ssid = "WiFi name";
const char *password = "WiFi password";

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)"Memory available in PSRAM : " +ESP.getFreePsram());

        //Connect to WiFi

        WiFi.begin(ssid, password);
        while (WiFi.status() != WL_CONNECTED){
                delay(100);
                Serial.print(".");
        }

        Serial.print("\nWiFi connected with IP : ");
        Serial.println(WiFi.localIP());

        Serial.println("Downloading JSON");
        HTTPClient http;
        http.useHTTP10(true);
        http.begin(url);
        http.GET();

        SpiRamJsonDocument doc(100000); //Create a JSON document of 100 KB
        DeserializationError error = deserializeJson(doc, http.getStream());
        if(error){
                Serial.print("deserializeJson() failed: ");
                Serial.println(error.c_str());
        }

        http.end();
        Serial.println((String)"JsonDocument Usage Memory: " + 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(){
}

Terminal output:

Memory available in PSRAM : 4194252
..............
WiFi connected with IP : 192.168.43.7
Downloading JSON
JsonDocument Usage Memory: 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/

...

Voir aussi

Comment utiliser la RAM externe sur l’ESP32 sur le site officiel ArduinoJson

Framework Arduino

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.

  • Cet exemple montre comment créer un tableau d’entiers de 1000 éléments stocké dans la PSRAM:

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 :

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() {
}

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