Comment ajouter de la mémoire de données (SRAM) en SPI ?

NIVEAU 4

Objectifs

  • Mettre en oeuvre une mémoire de données externe SPI

Pré-requis

SRAM 23LCV1024 de Microchip

Le composant 23LCV1024 de Microchip (documentation) est une mémoire à accès aléatoire, c’est à dire volatile – perte de l’information lorsqu’elle n’est plus alimentée – de type statique, c’est à dire sans nécessité de rafraîchir les données régulièrement. Elle peut être pilotée via une interface SPI jusqu’à 20~MHz.

Caractéristiques

Elle peut stocker 1 Mibits soient 128 kio, soit 2 x 256 x 256 adresses différentes. Son temps de réponse est d’environ 30 ns. Elle a un jeu d’instructions très limité (voir tableau ci-dessous). Il existe trois modes de transfert décrits dans la documentation : le mode séquentiel (SEQ), le mode page (PAGE) et le mode octet par octet (BYTE).

Instructions de la SRAM 23LCV1024 – Documentation de la 23LCV1024 / Table 2-1

Transfert de données

Pour pouvoir lire ou écrire une donnée dans cette mémoire à accès aléatoire, il est nécessaire de transmettre :

  • une commande, sur un octet, soit WRITE soit READ
  • une adresse, sur 3 octets (17 bits utilisés)
  • une donnée (ou plusieurs données) – transmise ou reçue, selon le mode lecture ou écriture choisi

Les figures suivantes montrent la lecture et l’écriture d’une donnée à une adresse particulière.

Transfert de données en SPI – Documentation de la 23LCV1024 / Figures 2-1 et 2-2

Utilisation de la SRAM avec la carte Nucleo L476RG

Connexion SPI entre la carte Nucleo L476RG et la SRAM 23LCV1024

La SRAM 23LCV1024 attend des commandes sous forme d’une transmission de type SPI. Il faut donc avant tout déclarer les broches utiles à la communication.

SPI my_sram(D11,D12,D13); // mosi, miso, sck
DigitalOut CS(D10);

Le câblage se fera alors de la façon suivante :

Une fois cette étape faite, il faut à présent initialiser la liaison, dans la fonction main par exemple, avec les paramètres adéquats : 8 bits par paquet, mode 0 et fréquence inférieure à 20 MHz.

my_sram.format(8,0);          
my_sram.frequency(100000);     

Bibliothèque de fonctions

Afin de simplifier l’utilisation de le SRAM externe, il peut être intéressant d’écrire quelques fonctions de base. En particulier, il peut être intéressant d’avoir des fonctions qui permettent :

  • d’initialiser la SRAM en mode octet par octet (BYTE_MODE) – void initRAM(void)
  • de stocker la donnée val à l’adresse passée en argument (3 octets) – void writeRAM(char val, char adH, char adC, char adL)
  • de récupérer une donnée à l’adresse passée en argument (3 octets) – char readRAM(char adH, char adC, char adL)

Cette bibliothèque peut aussi contenir les constantes suivantes :

#define    SRAM_WRMR_RAM   0x01
#define    SRAM_READ_CMD   0x03
#define    SRAM_WRITE_CMD  0x02
#define    SRAM_RSTIO      0xFF    
/* Mode de fonctionnement */
#define    SRAM_PAGE_MODE       0x80
#define    SRAM_BYTE_MODE       0x00
#define    SRAM_SEQ_MODE        0x40

Initialisation de la communication

Pour initialiser la communication entre la SRAM et la carte Nucleo L476RG, on peut suivre les étapes suivantes. Ces différentes instructions peuvent être mises dans une fonction nommée void initRAM(void).

void initRAM(void) {
    CS = 1;
    wait_ms(1);

Ces premières lignes permettent d’initialiser la broche CS en sortie et de lui affecter la valeur ‘1’.

    CS = 0;
    my_sram.write(SRAM_RSTIO);
    wait_ms(1);
    CS = 1;
    wait_ms(10);

Ensuite, on amorce la discussion en faisant baisser le signal CS associé à la SRAM. On envoie la commande de réinitialisation RSTIO (voir table 2-1 de la documentation). On refait ensuite passer CS à ‘1’ et on laisse un délai de 10 ms pour laisser le temps à la SRAM de s’initialiser.

    CS = 0;
    my_sram.write(SRAM_WRMR_RAM);
    my_sram.write(SRAM_BYTE_MODE);
    CS = 1;

Cette dernière partie permet d’écrire dans le registre de configuration du mode (commande WRMR_RAM) la commande permettant de passer dans un mode de transfert octet par octet (ici BYTE_MODE).

L’ensemble des commandes sont transmises à l’aide de la fonction void sendSPI(char data) de la bibliothèque spi.h.

Ecriture d’une donnée

Pour pouvoir envoyer une donnée à stocker dans la SRAM à une adresse particulière, il faut suivre les étapes suivantes, que l’on peut écrire dans une fonction nommée void writeRAM(char val, char adH, char adC, char adL).

    CS = 0;
    my_sram.write(SRAM_WRITE_CMD);
    my_sram.write(addr_h);
    my_sram.write(addr_m);
    my_sram.write(addr_l);
    my_sram.write(val);
    CS = 1;

Par exemple, pour stocker la valeur 0x33 à l’adresse 26.876 de la SRAM, il faut écrire :

int adresse = 26876;
writeRAM(0x33, adresse << 16, adresse << 8, adresse);

Lors de l’affectation des 3 octets des adresses, seul l’octet de poids faible est conservé.

Lecture d’une donnée

Pour pouvoir récupérer une donnée stockée dans la SRAM à une adresse particulière, il faut suivre les étapes suivantes, que l’on peut écrire dans une fonction nommée char SRAM_read_byte(char addr_h, char addr_m, char addr_l).

    char k;
    CS = 0;
    my_sram.write(SRAM_READ_CMD);
    my_sram.write(addr_h);
    my_sram.write(addr_m);
    my_sram.write(addr_l);
    k = my_sram.write(0xFF);
    CS = 1;
    return k;

Par exemple, pour récupérer la valeur stockée à l’adresse 26.876 de la SRAM, il faut écrire :

int adresse = 26876;
char data = 0;
data = readRAM(adresse << 16, adresse << 8, adresse);

Exemples d’utilisation

Initialisation de la RAM à zéro

Le programme doit contenir toutes les fonctions définies précédemment. Le début du programme sera alors le suivant.

#include "mbed.h"
#define    SRAM_WRMR_RAM   0x01
#define    SRAM_READ_CMD   0x03
#define    SRAM_WRITE_CMD  0x02
#define    SRAM_RSTIO      0xFF    
/* Mode de fonctionnement */
#define    SRAM_PAGE_MODE       0x80
#define    SRAM_BYTE_MODE       0x00
#define    SRAM_SEQ_MODE        0x40

void initRAM(void);
void writeRAM(char val, char adH, char adC, char adL);
char readRAM(char adH, char adC, char adL);

SPI my_sram(D11,D12,D13); // mosi, miso, sck
DigitalOut CS(D10);

Dans la fonction principale, on pourra alors suivre les étapes suivantes :

void main(void){
    int i, j;
    initRAM();

Ensuite, on parcourt l’ensemble des cases mémoires à l’aide de deux boucles imbriquées for, permettant de parcourir 256 * 256 cases. On appelle la fonction writeRAM() pour pouvoir stocker la valeur 0 (premier argument) à l’adresse pointée par les variables incrémentées par les boucles for.

    for(j = 0; j < 255; j++){
        for(i = 0; i < 255; i++){
            writeRAM(0, 0, j, i);
        }
    }
    for(j = 0; j < 255; j++){
        for(i = 0; i < 255; i++){
            writeRAM(0, 1, j, i);
        }
    }
}

Pour remplir l’ensemble des cases, il est nécessaire de parcourir 2 fois 256 * 256 cases mémoires (ce qui permet de remplir les 128 kio au final).

Générateur de signal

Un autre exemple pour tester le bon fonctionnement de ces fonctions est la réalisation d’un générateur de signal, à l’aide d’un convertisseur numérique analogique en sortie par exemple.

On va dans un premier temps initialiser le microcontroleur et la SRAM (ainsi que les autres périphériques au besoin) :

void main(void){
    int i, j;
    initRAM();

On va ensuite initialiser la RAM avec un motif particulier, par exemple ici, un signal triangulaire sur 256 niveaux (simple à réaliser, mais on pourrait faire d’autres motifs).

    for(j = 0; j < 255; j++){
        for(i = 0; i < 255; i++){
            if(j%2 == 0)   writeRAM(i, 0, j, i);
            else    writeRAM(255-i, 0, j, i);
        }
    }

Ce signal pré-enregistré va alors servir de référence pour le convertisseur numérique analogique (AD7303 par exemple).
On lit successivement les données contenues dans la RAM pour les envoyer sur le CNA.

    while(1){
        for(j = 0; j < 255; j++){
            for(i = 0; i < 255; i++){
                res = readRAM(0, j, i);
                __delay_ms(1);
            }
        }
    }

MInE Prototyper Prototyper avec Nucleo et MBED

Nucléo – Ajouter de la mémoire de données (SRAM) en SPI / 4