Monday, October 8, 2012

Accessing 93C46/93C46N serial EEPROM

I have found a few old EEPROM chips marked as 93C46N (DIP8):
It has only 128 bytes (to be exact 64 words), but it has serious advantage - more than 1 000 000 erase/write cycles, so I'm planning to utilize them for daily recording of some of my devices' indications.
93C46 is serial EEPROM which means it may be accessed via Arduino SPI.
Take a look at the datasheet and wire it up with your Arduino properly:
LED has been added to the circuit to indicate data transmission process and bypass capacitor wired up in order to reduce voltage swing.
Code:
  1 //
  2 // LED <--> Arduino:
  3 // Anode <--> Digital 8 (via 220 Ohm resistor)
  4 // Cathode <--> GND
  5 //
  6 // 93C46 <--> Arduino:
  7 // CS <--> Digital 53 (SS)
  8 // SCK <--> Digital 52 (SCK)
  9 // DI <--> Digital 51 (MOSI)
 10 // DO <--> Digital 50 (MISO)
 11 // VCC <--> 5V
 12 // GND <--> GND
 13 //
 14 
 15 #include <avr/io.h>
 16 #include <util/delay.h>
 17 
 18 #define CLEAN 0b00000000
 19 
 20 #define DDR93C46  DDRB
 21 #define PORT93C46 PORTB
 22 #define PIN93C46  PINB
 23 
 24 #define SK PB1 //SCK - Digital 52
 25 #define DO PB3 //MISO - Digital 50
 26 #define DI PB2 //MOSI - Digital 51
 27 #define CS PB0 //SS - Digital 53
 28 
 29 #define READ  0x02 // 10 00ABCD - 0x02 ADDR
 30 #define EWEN1 0x00 // 00 11XXXX
 31 #define EWEN2 0x30 //           - 0x00 0x30
 32 #define ERASE 0x03 // 11 00ABCD - 0x03 ADDR
 33 #define ERAL1 0x00 // 00 10XXXX
 34 #define ERAL2 0x20 //           - 0x00 0x20
 35 #define WRITE 0x01 // 01 00ABCD - 0x01 ADDR
 36 #define WRAL1 0x00 // 00 01XXXX
 37 #define WRAL2 0x10 //           - 0x00 0x10
 38 #define EWDS1 0x00 // 00 00XXXX
 39 #define EWDS2 0x00 //           - 0x00 0x00
 40 
 41 #define SET_CS PORT93C46 |= (1 << CS)
 42 #define CLR_CS PORT93C46 &= ~(1 << CS)
 43 
 44 #define SET_SK {PORT93C46 |= (1 << SK); _delay_us(0.01);}
 45 #define CLR_SK PORT93C46 &= ~(1 << SK)
 46 
 47 #define SET_DI PORT93C46 |= (1 << DI)
 48 #define CLR_DI PORT93C46 &= ~(1 << DI)
 49 
 50 class SPI_93C46N {
 51 private:
 52   uint8_t Transfer(uint8_t data) {
 53     SPDR = data;
 54     while(!(SPSR & (1 << SPIF))); // Wait for SPI interrupt flag
 55     return SPDR;                  // Return byte gathered from SPI data register
 56   }
 57   void Opcode(uint8_t opcode, uint8_t address) {
 58     SET_CS;
 59     SPCR &= ~(1 << SPE);               // SPI disable
 60     _delay_us(1);
 61     CLR_SK;
 62     SET_DI;                            // Send start bit
 63     SET_SK;
 64     CLR_SK;
 65     SPCR |= (1 << SPE);                // SPI enable
 66     Transfer((opcode << 6) | address); // Transmit byte
 67   }
 68 public:
 69   SPI_93C46N() {
 70     SPCR =    CLEAN        // Set SPI control register
 71            & ~(1 << SPIE)  // SPI interrupt disable
 72            |  (1 << SPE)   // SPI enable
 73            & ~(1 << DORD)  // MSB first
 74            |  (1 << MSTR)  // SPI master device
 75            & ~(1 << CPOL)  // SPI
 76            & ~(1 << CPHA)  //  mode 0
 77            |  (1 << SPR1)  // XX/64 MHz
 78            & ~(1 << SPR0); //  speed
 79     SPSR =    CLEAN         // Init SPI status register
 80            & ~(1 << SPI2X); // SPI double speed
 81     DDR93C46 =    CLEAN      // Set up inputs/outputs
 82                |  (1 << SK)  // Serial clock output
 83                |  (1 << DI)  // Data input (MOSI) output
 84                |  (1 << CS)  // Chip select output
 85                & ~(1 << DO); // Data output (MISO) input
 86     PORT93C46 |= (1 << DO); // Data output (MISO) high
 87     CLR_DI;
 88     CLR_CS;    
 89     CLR_SK;
 90     _delay_us(1);
 91   }
 92   ~SPI_93C46N() {
 93     //
 94   }
 95   uint16_t Read(uint8_t address) {
 96     uint16_t data = 0;
 97     Opcode(READ, address);              // READ instruction
 98     CLR_DI;
 99     SET_SK;                             // "0" DUMMY bit
100     CLR_SK;
101     SPCR |=  (1 << CPHA);               // Invert PHA, very important!
102     data = Transfer(0);
103     data = (data << 8) | (Transfer(0));
104     _delay_us(1);
105     CLR_CS;
106     CLR_DI;
107     SPCR &= ~(1 << CPHA);   
108     return data;
109   }
110   void EraseWriteEnable(void) {
111     Opcode(EWEN1, EWEN2);   
112     CLR_DI;
113     CLR_CS;
114     SET_CS;
115     _delay_us(0.1);
116     while(!(PIN93C46 & (1 << DO))); // Wait 1
117     CLR_CS; 
118   }
119   void EraseWriteDisable(void) {
120     Opcode(EWDS1, EWDS2);
121     CLR_DI;
122     CLR_CS;
123     SET_CS;
124     _delay_us(0.1);
125     while(!(PIN93C46 & (1 << DO))); // Wait 1
126     CLR_CS; 
127   }
128   void Erase(uint8_t address) {
129     Opcode(ERASE, address);   
130     CLR_DI;
131     CLR_CS;
132     SET_CS;
133     _delay_us(0.1);
134     while(!(PIN93C46 & (1 << DO))); // Wait 1
135     CLR_CS; 
136   }
137   void EraseAll(void) {
138     Opcode(ERAL1, ERAL2);   
139     CLR_DI;
140     CLR_CS;
141     SET_CS;
142     _delay_us(0.1);
143     while(!(PIN93C46 & (1 << DO))); // Wait 1
144     CLR_CS; 
145   }
146   void Write(uint8_t address, uint16_t data) {
147     Opcode(WRITE, address);         // WRITE instruction
148     Transfer(data >> 8);            // Write data high byte to addreCS
149     Transfer(data & 0xFF);          // Write data low byte to addreCS
150     CLR_DI;
151     CLR_CS;
152     SET_CS;
153     _delay_us(0.1);
154     while(!(PIN93C46 & (1 << DO))); // Wait "1" 
155     CLR_CS;
156   } 
157   void WriteAll(uint16_t data) {
158     Opcode(WRAL1, WRAL2);           // WRITE instruction
159     Transfer(data >> 8);            // Write data high byte to addreCS
160     Transfer(data & 0xFF);          // Write data low byte to addreCS
161     CLR_DI;
162     CLR_CS;
163     SET_CS;
164     _delay_us(0.1);
165     while(!(PIN93C46 & (1 << DO))); // Wait "1" 
166     CLR_CS;
167   }
168 };
169 
170 void setup(void) {
171   Serial.begin(115200);
172   _delay_ms(100);
173   Serial.println("EEPROM writing/reading");
174   pinMode(8, OUTPUT);
175 }
176 
177 void loop(void) {
178   SPI_93C46N _93C46N;
179   int i;
180   uint16_t data;
181   _93C46N.EraseWriteEnable();
182 //  for(i = 0; i < 32; i++)
183 //    _93C46N.Write(i, 0xDEAD);
184 //  for(i = 32; i < 64; i++)
185 //    _93C46N.Write(i, 0xBEEF);
186   _93C46N.WriteAll(0xB00B);
187   _93C46N.EraseWriteDisable();
188   for(i = 0; i < 64; i++) {
189     data = _93C46N.Read(i);
190     analogWrite(8, (unsigned char)(data >> 8));
191     _delay_ms(50);
192     analogWrite(8, (unsigned char)data);
193     _delay_ms(50);
194     Serial.print(data, HEX);
195     Serial.print(" ");
196     if((i + 1) % 8 == 0)
197       Serial.println();
198   }
199   analogWrite(8, 0);
200   _delay_ms(10000);  
201 }

Results:
Download sketch.

4 comments:

  1. Thanks for sharing this sketch!

    I salvaged a similar IC labelled ATMEL480 93C46 (1K) and been trying to get it to work with an Arduino Uno board. I had to make some changes to your sketch as it wouldn't compile for the Uno: PB ports are not recognized as symbols by the compiler. Also the IC I got has an ORG pin for the memory layout: 128 x 8 or 64 x 16.

    It works well about 50% of the time when reading the memory in a loop. The loop either starts and keeps going on or gets stuck in the Transfer method for the first Read while waiting for the SPI interrupt flag.

    Since am new to the world of electronics, am not quite sure what to look for to fix this. I just ordered an oscilloscope, will definitely help a lot with debugging.

    Feel free to have a look at my sketch (and schema made with Fritzing) by using the following link:

    https://truck.it/p/VuQiSZ8iwP

    If you have any ideas, I'd appreciate any insights you might have!

    Cheers!

    ReplyDelete
  2. Thanks, your sketch works perfectly with my AT93C46B.

    ReplyDelete
  3. It works with my Mega only: not with Duemilanove or Uno...

    ReplyDelete
  4. how to select 128x8 layout in code? 64x16 works fine!

    ReplyDelete