Geheugenchips
Geheugenchips zijn meestal gemakkelijk aan te sluiten. Je moet dan wel de DIP uitvoering hebben met 8 pootjes. Deze kun je zo in een breadboard prikken. Ze zijn goed bruikbaar om gebruikersgegevens op te slaan. Je kunt ze echter nooit gebruiken om het programmageheugen van de Arduino uit te breiden.
Men maakt onderscheid tussen vluchtig en niet-vluchtig geheugen. Vluchtig geheugen heeft een voedingsspanning nodig, zodra die wegvalt zal de inhoud van het geheugen geleidelijk aan verdwijnen. De inhoud van niet-vluchtig geheugen blijft onveranderd als de voedingsspanning wegvalt. Binnen deze twee categorieën zijn er nog diverse soorten geheugen met elk hun eigen specifieke eigenschappen.
Goed te gebruiken bij Arduino projecten zijn:
- SRAM (Static Random Access Memory). Vluchtig geheugen. Snel te lezen en te schrijven. Als je dit wil gebruiken voor langdurige opslag dan moet je een batterij gebruiken. Dat kan acceptabel zijn voor bijvoorbeeld het bewaren van instellingen, maar zeker niet voor opslag van belangrijke data. Ga naar de paragraaf over SRAM chips
- EEPROM (Electronically Erasable Programmable Read-Only Memory). Niet-vluchtig geheugen. Vrij langzaam, maar erg goedkoop. Ze gebruiken in standby bijzonder weinig vermogen. Ga naar de paragraaf over EEPROM chips
- FRAM of F-RAM (Ferroelectric Random Access Memory). Snel niet-vluchtig geheugen. Ga naar de paragraaf over F-RAM chips
- Flash geheugen. Snel niet-vluchtig geheugen. Goedkoop, voor betrekkelijk grote geheugens geschikt. Beperkt aantal keer te beschrijven. Om het flash-geheugen te kunnen benaderen wordt meestal SPI gebruikt. Ga naar de paragraaf over flash chips
Sommige mensen maken zich zorgen over veroudering van EEPROM en FRAM. Dat is niet echt nodig, want bij de nieuwste types blijft de inhoud bij kamertemperatuur minstens 40 jaar in tact. Daarbij kan FRAM minimaal 10 miljard keer worden beschreven en gelezen en waarschijnlijk nog heel veel vaker. Grote kans dat een apparaat al lang is afgedankt voor de FRAM-chip het laat afweten. De door mij geteste EEPROM chips kunnen minimaal een miljoen keer worden beschreven. De flash geheugens houden de gegevens minstens 20 jaar vast en kunnen zo'n 100000 keer worden beschreven.
De maximale opslagcapaciteit van deze chips ligt rond de 64 MByte. Voor de flash geheugens die met SPI werken is dat veel meer. Voor het opslaan van grote hoeveelheden data en foto's kun je beter een SD kaartje gebruiken.
EEPROM met I²C interface
AT24C128, AT24256, 24AA512, 24LC512 en 24FC512
Aansluitingen
Deze chips zijn goede geheugenchips voor gebruik met de Arduino. Ik heb ze bij elkaar gezet omdat ze grotendeels met dezelfde software werken. Voor de AT24C16: zie verderop. Ze gebruiken I²C voor de communicatie met de Arduino. Hoewel deze chips van verschillende firma's zijn kun je dezelfde software gebruiken. De laatste drie letters geven de capaciteit van de chip aan in kilobits. Je moet dit getal delen door acht om het aantal kilobytes te krijgen. Door de I²C interface heb je maar twee signaaldraden nodig en je kunt meerdere geheugenchips tegelijk aansluiten, mits ze een verschillend adres hebben. Voor de voedingsspanning VCC kun je 5 volt of 3,3 volt nemen. Het adres is standaard 0x50, maar door VCC aan te sluiten op A0 en A1 (en bij sommige chips ook op A2) kun je het adres verhogen tot maximaal 0x53 (of 0x57). Deze pinnen moeten altijd aangesloten zijn op GND of op VCC. Daarnaast heeft de chip een WP (= write protect) pin. Als deze hoog is (VCC) dan kun je het geheugen alleen lezen, maar niet beschrijven. Bij de Atmel chips hoef je WP niet aan te sluiten als je deze niet gebruikt.
Het is om allerlei redenen vrij lastig om data in het het geheugen op te slaan. Bibliotheken en voorbeeldprogramma's die op Internet te vinden zijn bevatten vaak fouten, waardoor je denkt dat iets goed is weggeschreven, terwijl dat niet zo is. Uitlezen gaat wel makkelijk. Als je het geheugen byte voor byte beschrijft heb je deze problemen niet. Daarmee wordt het verzenden een stuk langzamer, maar voor veel toepassingen is dat nog altijd snel genoeg!
Arduino nano met EEPROM chip in bedrijf
Ik heb dit getest door het hele geheugen (64 kByte) van de 24LC512 vol te schrijven. Dat lukt met mijn Arduino Uno in 355 seconden. Alles teruglezen èn controleren gaat in 14 s. Met de Arduino nano waren de getallen exact hetzelfde. Om zeker te zijn heb ik verschillende sets getallen gebruikt: een oplopend volgnummer; een aflopend volgnummer en willekeurige getallen. Ik heb hierbij geen enkele lees- of schrijffout gevonden. Maar alle chips en Arduino's zijn verschillend, dus zou je voor kritische toepassingen eigenlijk de opgeslagen gegevens meteen terug moeten lezen en controleren.
De uitvoer na de eerste en tweede keer
Als je met dit programma je chip wilt testen, verander dan eerst MAXadres in de juiste waarde, volgens het commentaar in het programma. Als je hem op de maximum waarde laat staan dan werkt het programma nog steeds, maar dan wordt het geheugen wellicht 2 of 4 keer beschreven en dat kost dan evenredig meer tijd. Run het programma en zet de Seriële monitor aan. Je weet nog niet of het goed gegaan is, want de chip meldt eventuele fouten niet. Verander nu het woord true dat achter #define Schrijven staat in false. Upload de sketch nogmaals. Als de monitor laat zien dat het programma klaar is, dan zijn er geen fouten opgetreden. Als er wel een fout is, dan geeft hij het adres van de eerste foute byte en de waarde van die byte. Grote kans dat de fout op adres 0 of 1 optreedt: de chip heeft niets opgeslagen door een verkeerde aansluiting of is kapot.
Als de chip goed is bevonden, upload dan een ander programma of koppel de chip los, want je wilt niet dat de chip onnodig vaak beschreven wordt.
Klik hier om het testprogramma voor de EEPROM chip te zien
// Testprogramma voor (modules met) de EEPROM AT24C128, AT24256, 24AA512, 24LC512 of 24FC512 chip
// Run het programma eerst met #define Schrijven true en daarna met #define Schrijven false
#include <Wire.h>
#define Adres 0x50 // Het default I2C adres
#define Schrijven true // Maak hier false van als je het programma de tweede keer start
#define MAXadres 0xFFFF // Kies 0xFFFF voor 512; 0x7FFF voor 256 en 0x3FFF voor 128 kbit chips
int plaats = 0; // Startplaats in geheugen
void schrijfByte(byte waarde) {
Wire.beginTransmission(Adres);
Wire.write((byte)(plaats >> 8)); // Zend de meest significante byte van de plaats
Wire.write((byte)(plaats & 0xFF)); // Zend de minst significante byte van de plaats
Wire.write(waarde);
plaats++;
Wire.endTransmission(); delay(5); // Dit delay is noodzakelijk!
}
void setup() {
Serial.begin(9600);
Wire.begin();
Wire.beginTransmission(Adres);
if (Wire.endTransmission() != 0) {
Serial.print("Er is geen I2C apparaat gevonden met adres 0x"); Serial.println(Adres, HEX);
Serial.println("Controleer de aansluitingen en reset de Arduino dan.");
return; // Doe verder niets meer, het probleem moet eerst worden opgelost
}
if (Schrijven) {
int Start = millis();
Serial.print("Ik beschrijf "); Serial.print((long) MAXadres + 1); Serial.println(" geheugenplaatsen...");
unsigned int i = 0;
while (true) {
schrijfByte(i & 0xFF);
if (i == MAXadres) break;
i++;
}
Serial.print("Alles geschreven in "); Serial.print(millis() / 1000, 1); Serial.println(" s");
}
else {
Serial.println("De test wordt gestart");
int Start = millis();
Wire.beginTransmission(Adres);
Wire.write(0); // Start op geheugenplaats 0
Wire.write(0); //
Wire.endTransmission(); // Het adres is ingesteld, bij elke leesopdracht verhoogt de chip het adres vanzelf
unsigned int i = 0;
while (true) {
Wire.requestFrom(Adres, 1);
byte X = Wire.read();
if ( X != (i & 0xFF) ) {
Serial.print("Schrijffout op geheugenplaats "); Serial.println(i);
Serial.print("Gelezen "); Serial.print(X); Serial.print(", maar moet zijn "); Serial.println(i & 0xFF);
return; // Doe verder niets meer
}
if (i == MAXadres) break; // Klaar
i++;
}
Serial.print((long) i + 1); Serial.print(" geheugenplaatsen gecontroleerd in "); Serial.print(millis() / 1000, 1); Serial.println(" s");
}
}
void loop() { // Doe niets
}
Nog te doen: wegschrijven van teksten, integers en kommagetallen; praktische toepassing
EEPROM met I²C interface
AT24C16
Dit is een wat ouder type chip. Omdat wordt geadviseerd deze niet meer te gebruiken in nieuwe apparaten zijn ze nu supergoedkoop te krijgen. Voor veel Arduino toepassingen voldoen ze nog prima. Een toepassing is bijvoorbeeld een scorelijst bij een spel. Ik heb hier een voorbeeld beschreven.
Deze chip komt uit een serie met geheugens van 1 tot 16 kBit. Ik testte alleen die van 16 kBit (dat is 2 kByte). Deze chip gebruikt 8 I²C adressen. Dat komt omdat de chip maar één byte gebruikt om de geheugenplaats mee te specifieren en daarmee kun je maar 256 verschillende plaatsen in kwijt. Dat wordt opgelost door de volgende 256 geheugenplaatsen een hoger I²C adres te geven. Om 2 kByte te kunnen adresseren heb je 11 bits nodig. De drie bits die je in de byte tekort komt worden als het ware in het I²C adres opgenomen. In het testprogramma wordt dit automatisch geregeld. Ik heb mijn chip getest met 5 volt en hij werkt prima. Als jouw chip niet goed werkt, probeer dan als eerste een iets groter delay in te stellen. Je kunt met deze chip data per 16 bytes tegelijk wegschrijven, maar dat heb ik niet getest. Als je deze chip aansluit, dan hoef je de pinnen A0 t/m A2 niet aan te sluiten. Het is niet mogelijk meerdere AT24C16 chips tegelijk te gebruiken op dezelfde Arduino.
Klik hier om het testprogramma voor de AT24C16 te zien
// Testprogramma voor de AT24C16 EEPROM
#include <Wire.h>
#define Adres 0x50 // Beginadres, 0x51 t/m 0x57 gaan naar ander deel van het geheugen
void schrijfByte(int geheugenplaats, byte data) {
Wire.beginTransmission( Adres + (geheugenplaats >> 8) );
Wire.write(geheugenplaats & 0xFF);
Wire.write(data);
Wire.endTransmission();
}
byte leesByte(int geheugenplaats) {
Wire.beginTransmission( Adres + (geheugenplaats >> 8) );
Wire.write(geheugenplaats & 0xFF);
Wire.endTransmission();
Wire.requestFrom(Adres + (geheugenplaats >> 8), 1);
return Wire.read();
}
void setup() {
Wire.begin();
Serial.begin(9600);
Serial.println("Bezig met geheugen schrijven (duurt een tijdje)");
for (int i = 0; i < 0x7FF; i++) {
int x = (i % 10) + 10 * (i >> 8);
schrijfByte(i, x & 0xFF);
delay(5); // Maak niet te klein
}
Serial.println("Klaar met schrijven!");
Serial.println("Aan het lezen...");
for (int i = 0; i < 0x7FF; i++) {
int x = (i % 10) + 10 * (i >> 8);
int d = leesByte(i);
if ( d != x ) {
Serial.print("Fout op geheugenplaats "); Serial.println(i);
Serial.print("Verwacht was "); Serial.print(x); Serial.print(", gelezen: "); Serial.print(d);
return;
}
delay(5);
}
Serial.print("Geen fouten gevonden!");
}
void loop() {
}
F-RAM met I²C interface
F-Ram is veel sneller dan alle andere soorten geheugens, die op deze pagina genoemd zijn. Ik testte de veel gebruikte FM24C64 chip. Volgens de fabrikant kan deze direct in de plaats van een EEPROM chip worden gezet. Helaas waren mijn eerste twee exemplaren defect, maar ze werkten gedeeltelijk waardoor ik erg veel tijd kwijt was met testen. Sluit de pinnen A0, A1 en A2 en WP aan op GND (het hangt van het merk af of dit echt nodig is of niet, doe het dus voor alle zekerheid). Merkwaardig is dat WP (Write Protect) niet het hele geheugen beschermt tegen beschrijven, maar slechts het bovenste kwart van het geheugen.
Een goed exemplaar bleek inderdaad te werken met het programma dat hierboven staat bij de paragraaf over de EEPROM chip. Vervang voor je gaat testen MAXadres door 0x1FFF. Je zult met dit programma niet merken dat de chip veel sneller is omdat de test steeds per byte wegschrijft. Je kunt echter ook veel bytes direct na elkaar wegschrijven en lezen; dat is dan wel erg snel. Je zou kunnen testen of de EEPROM bibliotheken voor de Arduino je daarbij kunnen helpen. Wat je niet moet doen is de FM24I2C.h bibliotheek gebruiken, want die is niet goed en het voorbeeld programma dat erbij zit is zo slecht, dat de chip zelfs lijkt te werken als er niet eens een chip is aangesloten.
Deze chip bewaart zijn data minimaal 10 jaar. Je kunt elke geheugenplaats minstens 10 miljard keer herschrijven.
SRAM met SPI interface
Ik testte de veel gebruikte 23K256 SRAM chip. Deze heeft 256/8 = 32 kByte vluchtig geheugen. Dit geheugen kun je bijvoorbeeld gebruiken voor tijdelijke opslag van tussenresultaten in berekeningen. Als je de chip loskoppelt van de spanning dan is het geheugen snel weg. Deze chip werkt op 3,3 volt en boven 4,5 volt kan hij kapot gaan. De meeste Arduino's werken op 5 volt. Dat betekent dat je de spanningen van de Arduino moet verlagen via weerstanden of dat je een level shifter van 3,3 naar 5 volt moet aansluiten. In beide gevallen komt dit de overzichtelijkheid van de schakeling niet ten goede. Als je weerstanden gebruikt dan moet dat alleen bij signalen die naar de chip toegaan, niet die naar de Arduino toegaan. Dus niet bij MISO (of DO = data out, of SO = Serial Data Out, of hoe de fabrikant deze aansluiting ook noemt). Een level shifter is hier de betere oplossing en ze zijn zo goedkoop dat ik aanraad er minimaal een te kopen. Koop een bidirectionele, maar dat zijn de meeste ook.
Op internet, onder de officiële Arduino site is een voorbeeldprogramma en een bibliotheek te vinden. Ik heb hiermee echter nogal veel problemen ondervonden. Ik raad deze bibliotheek dan ook niet aan.Uitvoer van het voorbeeldprogramma
Het is goed mogelijk om de chip te programmeren zonder die bibliotheek, zoals uit onderstaand voorbeeld blijkt. Je kunt dit voorbeeld geschikt maken voor jouw doeleinden zonder de functies aan te hoeven passen. Zo kun je zelf kiezen of je char of byte wilt gebruiken voor het weg te schrijven array. Ook mag je de getallen in dat array, terwijl je programma loopt, laten berekenen. Houd er wel rekening mee dat zodra je een buffer wegschrijft, de inhoud van die buffer verandert. Als dat een probleem is in jouw toepassing, dan zul je de data eerst moeten kopiëren in een andere variabele.
Klik hier om het voorbeeld voor de SRAM chip te zien
// Voorbeeldprogramma voor SRAM geheugenchip
// Gebaseerd op vergelijkbaar programma voor de 23LC1024 chip van J. B. Gallaher (2016)
#include <SPI.h>
#define WRSR 1 // Schrijf naar het status register van de chip
#define Lezen 3
#define Schrijven 2
#define Bytes 0 // Schrijf of lees een byte
#define Reeks 64 // Schrijf of lees een reeks gegevens tegelijk
#define CS 10 // Verbind pin 10 van de Arduino met CS van de chip
char Zin[28] = "Dit was de tafel van zeven."; // Zin die wordt weggeschreven
char buffer[28]; // Hier komt de teruggelezen zin in
void setup(void) {
unsigned int geheugenplaats; // Zet hierin de geheugenplaats waar je wilt starten
pinMode(CS, OUTPUT);
Serial.begin(9600);
SPI.begin();
Serial.print("Geschreven bytes: ");
kiesMode(Bytes); // Noodzakelijk zodra je van mode verandert !
byte data = 7;
for (byte i = 0; i <= 9; i++) {
geheugenplaats = i + 77; // Begin bij geheugenplaats 77
schrijfByte(geheugenplaats, data);
Serial.print(data); if (i < 9) Serial.print(", "); else Serial.println();
data += 7; // De tafel van 7...
}
Serial.print("Gelezen bytes: ");
for (byte i = 0; i <= 9; i++) {
geheugenplaats = i + 77;
data = leesByte(geheugenplaats);
Serial.print(data); if (i < 9) Serial.print(", "); else Serial.println();
}
Serial.print("\nGeschreven regel: ");
kiesMode(Reeks); // Noodzakelijk zodra je van mode verandert !
for (byte i = 0; i < sizeof(Zin); i++) Serial.print(Zin[i]); // Doe dit voor je SPI.transfer aanroept!
schrijfBuffer(0, Zin, sizeof(Zin)); // Schrijf Zin in het geheugen vanaf plaats 0
Serial.print("\nGelezen regel: ");
leesBuffer(0, buffer, sizeof(buffer)); // Lees de data terug in de buffer
for (byte i = 0; i < sizeof(buffer); i++) Serial.print(buffer[i]); // print de buffer
Serial.println();
}
void loop() { // We doen alles maar een keer
}
void kiesMode(char Mode) { // Kies schrijven/lezen per byte of per hele buffer
digitalWrite(CS, LOW); // CS laag: begin opdracht
SPI.transfer(WRSR); // Zet de chip in de schrijfstand
SPI.transfer(Mode);
digitalWrite(CS, HIGH); // CS hoog: einde opdracht
}
byte leesByte(uint16_t geheugenplaats) {
digitalWrite(CS, LOW);
SPI.transfer(Lezen);
SPI.transfer((byte)(geheugenplaats >> 8)); // zend hoge byte van de geheugenplaats
SPI.transfer((byte)geheugenplaats); // zend lage byte van de geheugenplaats
byte read_byte = SPI.transfer(0); // lees de byte, de 0 heeft geen betekenis
digitalWrite(CS, HIGH);
return read_byte;
}
void schrijfByte(uint16_t geheugenplaats, byte data) {
digitalWrite(CS, LOW);
SPI.transfer(Schrijven);
SPI.transfer((byte)(geheugenplaats >> 8));
SPI.transfer((byte)geheugenplaats);
SPI.transfer(data); // schrijf de byte
digitalWrite(CS, HIGH);
}
void schrijfBuffer(uint16_t geheugenplaats, byte *data, uint16_t aantal) { // schrijf buffer
digitalWrite(CS, LOW);
SPI.transfer(Schrijven);
SPI.transfer((byte)(geheugenplaats >> 8));
SPI.transfer((byte)geheugenplaats);
SPI.transfer(data, aantal); // schijf aantal bytes van de buffer
digitalWrite(CS, HIGH);
}
void leesBuffer(uint16_t geheugenplaats, byte *data, uint16_t aantal) {
digitalWrite(CS, LOW);
SPI.transfer(Lezen);
SPI.transfer((byte)(geheugenplaats >> 8));
SPI.transfer((byte)geheugenplaats);
SPI.transfer(data, aantal);
digitalWrite(CS, HIGH);
}
Voor chips met meer geheugen moet je een kleine aanpassing doen. (Beschrijving komt zodra ik een chip heb waarmee ik dat kan testen)
Flash geheugen met SPI interface
Misschien wel de bekendste flash geheugen chips zijn de Winbond chips. Deze zijn er met verschillende capaciteiten (1 MB tot 16 MB). Ik testte de W25Q80. Deze heeft een capaciteit van 1 MB geheugen. Voor deze en soortgelijke chips is er een universele bibliotheek: SPIMemory.h. Deze kan gemakkelijk via de bibliotheekbeheerder van de IDE geïnstalleerd worden.
Zodra je dat gedaan hebt zijn er voorbeelden beschikbaar in het menu bestand → voorbeelden → SPIMemory. Ik testte FlashDiagnostics.ino en nog een paar andere. Deze sketch gaf als dat capaciteit 8388608 bits. Dat is exact 1 MB of - beter - 1 MiB (= 220 bytes). In tegenstelling tot harde schijven en USB sticks krijg je dus wel precies het aantal bytes dat je verwacht.
Ik testte ook de Winbond 25Q128FVIQ. Deze heeft 2MB geheugen. Hij werkt ook prima met deze bibliotheek.
De chips die ik kocht kunnen tegen 5 volt, maar je moet ze eigenlijk gebruiken met 3,3 volt. Er zijn er ook die zelfs op 1,8 volt werken. Als je een lage-spanningsvariant hebt dan moet je ofwel weerstanden gebruiken, ofwel een level shifter.
Sluit de chip als volgt aan op een Arduino Uno:
- pin 1 ¬CS gaat naar pin 10
- pin 2 MISO gaat naar pin 12
- pin 3 ¬WP (not write protect) hoef je niet aan te sluiten
- pin 6 SCK gaat naar pin 13
- pin 5 MOSI gaat naar pin 11
- pin 7 ¬H (not hold) gaat naar VCC
- pin 8 gaat naar VCC en pin 4 gaat naar GND
Als je een andere Arduino hebt, dan moet je zelf nagaan hoe je dit moet aansluiten. Je kunt dit onder andere vinden op https://www.arduino.cc/en/reference/SPI.
Tip: als je Arduino een ICSP header heeft (het blokje met de 6 pinnen), dan kun je deze ook gebruiken in plaats van de hierboven genoemde pinnen; de aansluiting is dan voor elk type Arduino met een ICSP header gelijk.
Ik teste ook een MX25L8005 chip. Dit is een compatibele 1 MByte chip van een ander merk. Voor de zekerheid testte ik alleen met 3,3 V en dan werkt hij prima.