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).
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.
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