در بسیاری از مدارات امبدد جهت ذخیره پارامتر های مختلف ، برنامه های قابل اجرا و… از حافظه های خارجی استفاده می شود . به چند دلیل از حافظه های خارجی استفاده می کنند که مهمترین آن ها ارتقاء حافظه MCU و همچنین اطمینان پذیری بالای حافظه های خارجی است . به طور مثال در پروژه هایی که از حافظه EEPROM زیاد استفاده می شود طراح ها بیشتر از حافظه خارجی استفاده می کنند ( مانند مدارات گیرنده ریموت ) . مدل های مختلفی از حافظه های خارجی وجود دارد که هر کدام تکنولوژی خاص خود را دارند و از رابط های مختلف برای ارتباط استفاده میکنند . به طور مثال سری های AT24C شرکت Atmel چیپ های EEPROM هستند که می توان از طریق ارتباط I2C بر روی آن ها نوشت یا خواند . در این بین چیپ های W25Qxx نیز چیپ های حافظه Flash هستند که تکنولوژی بالاتری نسبت به EEPROM ها دارند . حافظه های Flash سرعت های بالاترd از 100MHz دارند که می توان از آن ها برای ذخیره برنامه های کاربردی استفاده کرد . به طور مثال SoC های ESP8266EX فاقد حافظه Flash داخلی هستند و از طریق یک چیپ W25Qxx برنامه کاربر را اجرا می کنند . این چیپ ها از ارتباط SPI پشتیبانی میکنند که امکان ارتباط با سرعت بالا را فراهم می سازد . مدل های مختلفی از IC های سری W25Qxx وجود دارد که تنها در حجم حافظه با همدیگر متفاوت هستند . نامگذاری این چیپ ها به این صورت است که بعد از W25Q مقدار حجم حافظه آن ها بر حسب مگابیت درج می شود . به طور مثال چیپ W25Q32 حافظه ای 32 مگابیتی یا 4 مگابایتی است . در این آموزش به نحوه راه اندزای این حافظه ها با استفاده از آردوینو خواهیم پرداخت . برای تست این چیپ ها بهتر است از ماژول آن استفاده کنید .

مشخصات :

  • ولتاژ تغذیه 2.7 تا 3.6 ولت
  • 4 مگابایت حافظه Flash
  • رابط ارتباطی SPI
  • سرعت SPI 104MHz تا 416MHz
  • نگهداری دیتا تا 20 سال در دمای 25 درجه سانتی گراد
  • امکان نوشتن و پاک کردن تا 100000 بار

 

پین های ماژول W25Qxx :

 

VCC ولتاژ تغذیه

CS انتخاب چیپ ( در ارتباط SPI)

DO خروجی دیتا در ارتباط I2C

GND پین زمین

CLK ورودی پالس ساعت

DI ورودی دیتا

 

راه اندازی ماژول W25Qxx با آردوینو :

 

#include <SPI.h>

#define writeEnable       0x06 // Address Write Enable
#define writeDisable      0x04 // Address Write Disable
#define chipErase         0xc7 // Address Chip Erase
#define readStatusReg1    0x05 // Address Read Status
#define readData          0x03 // Address Read Data
#define pageProgramStat   0x02 // Address Status Page Program
#define chipCommandId     0x9f // Address Status Read Id


boolean g_command_ready(false);
String g_command;

/*
print_page_bytes() is a simple helper function that formats 256 bytes
 */
void print_page_bytes(byte *page_buffer) {
  char buf[10];
  for (int i = 0; i < 16; ++i) {
    for (int j = 0; j < 16; ++j) {
      sprintf(buf, "%02x", page_buffer[i * 16 + j]);
      Serial.print(buf);
    }
    Serial.println();
  }
}

/*

This functions map to user commands. wrap the low-level calls with 
print/debug statements to read
*/

/* 
 The chip command id  is fairly generic, just to verify function setup 
 */
void chipCmdIda(void) {
  Serial.println("Set Command: chipCmdIda");
  byte b1, b2, b3;
  chipCmdId(&b1, &b2, &b3);
  char buf[128];
  sprintf(buf, "ID: %02xh\nMemory Type: %02xh\nCapacity: %02xh",
    b1, b2, b3);
  Serial.println(buf);
  Serial.println("Ready");
} 

void chip_erase(void) {
  Serial.println("command: chip_erase");
  _chip_erase();
  Serial.println("Ready");
}

void read_page(unsigned int page_number) {
  char buf[80];
  sprintf(buf, "command: read_page(%04xh)", page_number);
  Serial.println(buf);
  byte page_buffer[256];
  _read_page(page_number, page_buffer);
  print_page_bytes(page_buffer);
  Serial.println("Ready");
}

void read_all_pages(void) {
  Serial.println("command: read_all_pages");
  byte page_buffer[256];
  for (int i = 0; i < 4096; ++i) {
    _read_page(i, page_buffer);
    print_page_bytes(page_buffer);
  }
  Serial.println("Ready");
}

void write_byte(word page, byte offset, byte databyte) {
  char buf[80];
  sprintf(buf, "command: write_byte(%04xh, %04xh, %02xh)", page, offset, databyte);
  Serial.println(buf);
  byte page_data[256];
  _read_page(page, page_data);
  page_data[offset] = databyte;
  _write_page(page, page_data);
  Serial.println("Ready");
}


void chipCmdId(byte *b1, byte *b2, byte *b3) {
  digitalWrite(SS, HIGH);
  digitalWrite(SS, LOW);
  SPI.transfer(chipCommandId);
  *b1 = SPI.transfer(0); // manufacturer id
  *b2 = SPI.transfer(0); // memory type
  *b3 = SPI.transfer(0); // capacity
  digitalWrite(SS, HIGH);
  not_busy();
}  

/*
 See the timing diagram in section 9.2.26 of the data sheet, "Chip Erase (C7h / 06h)". (Note:
 */
void _chip_erase(void) {
  digitalWrite(SS, HIGH);
  digitalWrite(SS, LOW);  
  SPI.transfer(writeEnable);
  digitalWrite(SS, HIGH);
  digitalWrite(SS, LOW);  
  SPI.transfer(chipErase);
  digitalWrite(SS, HIGH);

  /* See notes on rev 2 
  digitalWrite(SS, LOW);  
  SPI.transfer(writeDisable);
  digitalWrite(SS, HIGH);
  */
  not_busy();
}

/*
 * See the timing diagram in section 9.2.10 of the
 * data sheet located below, "Read Data (03h)".
 */
void _read_page(word page_number, byte *page_buffer) {
  digitalWrite(SS, HIGH);
  digitalWrite(SS, LOW);
  SPI.transfer(readData);

  // Construct the 24-bit address from the 16-bit page
  // number and 0x00, since we will read 256 bytes (one
  // page).
  SPI.transfer((page_number >> 8) & 0xFF);
  SPI.transfer((page_number >> 0) & 0xFF);
  SPI.transfer(0);
  for (int i = 0; i < 256; ++i) {
    page_buffer[i] = SPI.transfer(0);
  }
  digitalWrite(SS, HIGH);
  not_busy();
}
 
/*
 * See the timing diagram in section 9.2.21 of the
 * data sheet, "Page Program (02h)".
 */
void _write_page(word page_number, byte *page_buffer) {
  digitalWrite(SS, HIGH);
  digitalWrite(SS, LOW);  
  SPI.transfer(writeEnable);
  digitalWrite(SS, HIGH);
  digitalWrite(SS, LOW);  
  SPI.transfer(pageProgramStat);
  SPI.transfer((page_number >>  8) & 0xFF);
  SPI.transfer((page_number >>  0) & 0xFF);
  SPI.transfer(0);
  for (int i = 0; i < 256; ++i) {
    SPI.transfer(page_buffer[i]);
  }
  digitalWrite(SS, HIGH);
  /* See notes on rev 2
  digitalWrite(SS, LOW);  
  SPI.transfer(writeDisable);
  digitalWrite(SS, HIGH);
  */
  not_busy();
}

/* 
 * See section 9.2.8 of the datasheet
 */
void not_busy(void) {
  digitalWrite(SS, HIGH);  
  digitalWrite(SS, LOW);
  SPI.transfer(WB_READ_STATUS_REG_1);       
  while (SPI.transfer(0) & 1) {}; 
  digitalWrite(SS, HIGH);  
}

/*
 * string, setting a boolean used by the loop() routine
 * as a dispatch trigger.
 */
void serialEvent() {
  char c;
  while (Serial.available()) {
    c = (char)Serial.read();
    if (c == ';') {
      g_command_ready = true;
    }
    else {
      g_command += c;
    }    
  }
}

void setup(void) {
  SPI.begin();
  SPI.setDataMode(0);
  SPI.setBitOrder(MSBFIRST);
  Serial.begin(9600);
  Serial.println("");
  Serial.println("Ready"); 
}

/*
 */
void loop(void) {
  if (g_command_ready) {
    if (g_command == "chipCmdIda") {
      chipCmdIda();
    }
    else if (g_command == "chip_erase") {
      chip_erase();
    }
    else if (g_command == "read_all_pages") {
      read_all_pages();
    }
    // A one-parameter command...
    else if (g_command.startsWith("read_page")) {
      int pos = g_command.indexOf(" ");
      if (pos == -1) {
        Serial.println("Error: Command 'read_page' expects an int operand");
      } else {
        word page = (word)g_command.substring(pos).toInt();
        read_page(page);
      }
    }
    // A three-parameter command..
    else if (g_command.startsWith("write_byte")) {
      word pageno;
      byte offset;
      byte data;
      
      String args[3];
      for (int i = 0; i < 3; ++i) {
        int pos = g_command.indexOf(" ");
        if (pos == -1) {
          Serial.println("Syntax error in write_byte");
          goto done;
        }
        args[i] = g_command.substring(pos + 1);
        g_command = args[i];
      }
      pageno = (word)args[0].toInt();
      offset = (byte)args[1].toInt();
      data = (byte)args[2].toInt();
      write_byte(pageno, offset, data);
    }
    else {
      Serial.print("Invalid command sent: ");
      Serial.println(g_command);
    }
done:
    g_command = "";
    g_command_ready = false;
  }
}

 

 

 

 

 

Tags:
About Author: USER_4