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.

Sunday, September 30, 2012

Arduino Mega + URM04 v2.0 + SPI LCD

As I noticed in previous post there is a way to connect your URM04 v2.0 ultrasonic sensor to Arduino without IO expansion shield.
Initial circuit is instable because it is extremely sensitive to any electrical interference due to the fact that it doesn't have appropriate "protection" - even if you just bring your hand too close to the wires (in case if your body is not grounded) circuit functioning will be broken and you will have to reset Arduino.
This issue may be fixed easily by adding several components: line termination resistor, decoupling (bypass) capacitor, pull-up, pull-down and load resistors - to the circuit (refer to URM04 v2.0 wiki page):
"Upgraded" circuit is stable and works great:
I have also connected  128x64 SPI LCD module to display measured distance on its screen.
Provided SPI LCD library does not work with Arduino 1.0 IDE (only with Arduino 0.23 AFAIK), so I've modified it a bit to correct this confusion - LCD12864RSPI.7z.
Coding:
// SPI LCD <--> Arduino:
// SID <-> Digital 9
// CS <-> Digital 8
// SCK <-> Digital 3
// GND <-> GND
// (?) <-->
// VCC <-> 5V
//
// URM04 v2.0 <--> RS485 board:
// + <--> VCC
// A <--> A
// B <--> B
// - <--> GND
//
// RS485 circuit <--> Arduino:
// VCC <--> 5V
// 1 <--> Digital 19 (RX1)
// 2 <--> Digital 2
// 3 <--> Digital 18 (TX1)
// GND <--> GND
//
#include <LCD12864RSPI.h>
//
//#define AR_SIZE( a ) sizeof( a ) / sizeof( a[0] )
//unsigned char wangzhi[]=" www.DFRobot.com ";//
//unsigned char en_char1[]="ST7920 LCD12864 ";//
//unsigned char en_char2[]="Test, Copyright ";//
//unsigned char en_char3[]="by DFRobot ---> ";//
char lol[] ="LOL!            ";
char temp[16];
//
// Pin number to enable XBee expansion board V5
int EN = 2;
//
// Measure distance using the URM04V2 ultrasonic sensor
void measureDistance(byte device) {
  digitalWrite(EN, HIGH);
  // Trigger distance measurement
  uint8_t DScmd[6] = {0x55, 0xaa, device, 0x00, 0x01, 0x00};   
  for(int i = 0; i < 6; i++) {
    Serial1.write(DScmd[i]);
    DScmd[5] += DScmd[i];
  }
  delay(30);
  // Send command to read measured distance 
  uint8_t STcmd[6] = {0x55, 0xaa, device, 0x00, 0x02, 0x00};
  for(int i = 0; i < 6; i++) {
    Serial1.write(STcmd[i]);
    STcmd[5] += STcmd[i];
  }
  delay(3);
}
//
// Return last measured distance by the URM04V2 ultrasonic sensor
// -1 means the last measurement is out of range or unsuccessful
int readDistance() {
  uint8_t data[8];
  digitalWrite(EN, LOW);
  boolean done = false;
  int counter = 0;
  int result = -1;
  while(!done) {
    int bytes = Serial1.available();
    if(bytes == 8) {
      for(int i = 0; i < 8; i++) {
        data[i] = Serial1.read();
      }
      result = (int)data[5] * 256 + (int)data[6];
      done = true;
    }
    else {
      delay(10);
      counter++;
      // If failed to read measured data for 5 times, give up and return -1
      if(counter == 5) {
        done = true;
      }
    }
  }
  return result;
}
//
void setup() {
  LCDA.Initialise();
  delay(100);
  LCDA.DisplayString(0, 0, (unsigned char *)lol, 16);
  delay(1000); 
  pinMode(EN, OUTPUT);
  delay(250);
  Serial.begin(19200);
  delay(250);
  Serial1.begin(19200);
  delay(250);
  digitalWrite(EN, HIGH);
  delay(1000);
}
//
void loop() {
  measureDistance(0x11);
  int distance = readDistance();
  LCDA.CLEAR();
  delay(100);
  memset(lol, ' ', 16);
  sprintf(lol, "%s cm", dtostrf(distance, 0, 0, temp));
  LCDA.DisplayString(0, 0, (unsigned char *)lol, 16);
  delay(1000);
}
Results:

Sunday, September 16, 2012

Connecting URM04 v2.0 ultrasonic sensor to Arduino

If you have bought URM04 v2.0 ultrasonic sensor but forgot to buy IO expansion shield (which is required to connect it to your Adruino), you still may use simple workaround to achieve your goal.
All you need is a few wires and RS485 chip - MAX485 or its analogue (I have used ST485):
Wire up your Arduino, RS485 and URM04 as it shown at the picture:
If ultrasonic sensor is connected properly then its LED will blink four times after powering on (also it blinks every time the data is transmitted).
Prepare sketch in Arduino IDE:
// Pin number to enable XBee expansion board V5
int EN = 2;
//
// Measure distance using the URM04V2 ultrasonic sensor
void measureDistance(byte device) {
  digitalWrite(EN, HIGH);
  // Trigger distance measurement
  uint8_t DScmd[6] = {0x55, 0xaa, device, 0x00, 0x01, 0x00};   
  for(int i = 0; i < 6; i++) {
    Serial1.write(DScmd[i]);
    DScmd[5] += DScmd[i];
  }
  delay(30);
  // Send command to read measured distance 
  uint8_t STcmd[6] = {0x55, 0xaa, device, 0x00, 0x02, 0x00};
  for(int i = 0; i < 6; i++) {
    Serial1.write(STcmd[i]);
    STcmd[5] += STcmd[i];
  }
  delay(3);
}
//
// Return last measured distance by the URM04V2 ultrasonic sensor
// -1 means the last measurement is out of range or unsuccessful
int readDistance() {
  uint8_t data[8];
  digitalWrite(EN, LOW);
  boolean done = false;
  int counter = 0;
  int result = -1;
  while(!done) {
    int bytes = Serial1.available();
    if(bytes == 8) {
      for(int i = 0; i < 8; i++) {
        data[i] = Serial1.read();
      }
      result = (int)data[5] * 256 + (int)data[6];
      done = true;
    }
    else {
      delay(10);
      counter++;
      // If failed to read measured data for 5 times, give up and return -1
      if(counter == 5) {
        done = true;
      }
    }
  }
  return result;
}
//
void setup() {
  pinMode(EN, OUTPUT);
  delay(250);
  Serial.begin(19200);
  delay(250);
  Serial1.begin(19200);
  delay(250);
  digitalWrite(EN, HIGH);
  delay(1000);
}
//
void loop() {
  measureDistance(0x11);
  int distance = readDistance();
  Serial.println(distance);
  delay(1000);
}
Upload it to your board and open Serial Monitor to see the result - distance to the closest object in centimeters.
Important Note: I have Arduino Mega, so I'm using serial port 1 (Serial1 - RX1/TX1) to connect to URM, if you have Arduino UNO or another board model which has only one serial port (Serial - RX0/TX0), then you should connect URM to RX0/TX0 appropriately and replace Serial1.-commands with Serial.-commands in the sketch. Also in that case you will have to unplug wires from RX0/TX0 before uploading your code to the board because this port is also used by Arduino to connect with PC and that's why it's okay if you will see strange "Ua" character in Serial Monitor - just ignore it.