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.
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.
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érêt d’une RAM externe est de pouvoir soulager la RAM interne pour le stockage temporaire de données importantes.
On peut par exemple :
Manipuler des pages WEB entières
Manipuler de gros fichiers JSON
Faire un serveur web performant
Créer d’énormes tableaux
Manipuler les données de gros fichiers
Cette section propose des exemples d’utilisation de la PSRAM. Ils permettent de comprendre comment utiliser les fonctions 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 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
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ée 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_lignes;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() {
}
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_
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.
Voir aussi
Pour des informations plus poussées, consulter la documentation officielle sur la PSRAM