Запись на внешнюю ЕЕПРОМку по i2c

5N62V
Offline
Зарегистрирован: 25.02.2016

Всем  бобра!

Попытался писать данные  на еепромку AT24C32  фреймами. 

Собсно код:

#define EEPROM_ADDR_WRITE 0b10100000
#define EEPROM_ADDR_READ 0b10100001

unsigned int counter = 0;
byte buffer[128] = {0};

void setup() {
Serial.begin(9600);
TWBR = 72; // i2c 100kHz, CPU 16MHz
 Serial.println("setup");
 for(uint8_t i = 0; i<128; i++)buffer[i] = i;
}

void loop() {
  Serial.println("loop");
  uint32_t time = micros();

i2c_writePage(0,0,&buffer[0],32);//записываем первые 32байта
time = micros()-time;
Serial.println(time);
delayMicroseconds(600);
i2c_writePage(0,32,&buffer[32],32);//записываем вторые 32байта
delayMicroseconds(600);
i2c_readPage(0,0,32); //читаем первые 32байта
delayMicroseconds(600);
i2c_readPage(0,32,32);//читаем вторые 32байта
Serial.println("Stop");
while(1) ;

}


 void i2c_start(){
TWCR = 0b10100100;// TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN)
while(! (TWCR&(1<<7)));
}

void i2c_stop(){
TWCR = 0b10010100;// TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN)  
}

void i2c_sendByte (unsigned char b){
  TWDR = b;
  TWCR = 0b10000100; //TWCR = (1<<TWINT)|(1<<TWEN)
  while(! (TWCR&(1<<7)));
}

uint8_t i2c_readByte(){
 // TWCR = 0b11000100;// TWCR = (1<<TWINT)|(1<<TWEA)|(1<<TWEN);
 TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWEA);
  while(! (TWCR&(1<<7)));
  return TWDR;
}

uint8_t i2c_readLastByte(){
  TWCR = 0b10000100;// TWCR = (1<<TWINT)|(1<<TWEN);
  while(! (TWCR&(1<<7)));
  return TWDR;
}


void i2c_writePage(uint8_t a, uint8_t b, uint8_t * buf, uint8_t len){
i2c_start();
i2c_sendByte(EEPROM_ADDR_WRITE);
i2c_sendByte(a);
i2c_sendByte(b);
  for (uint8_t i = 0; i<len; i++) {
    i2c_sendByte(*(buf+i));

    }
i2c_stop();  
}

void i2c_readPage(uint8_t a, uint8_t b, uint8_t len){
  uint8_t zzz;
i2c_start();
i2c_sendByte(EEPROM_ADDR_WRITE);
i2c_sendByte(a);
i2c_sendByte(b); 
i2c_start();
i2c_sendByte(EEPROM_ADDR_READ);
for(uint8_t i = 0;i<len-1;i++) {Serial.print(i2c_readByte()); Serial.print(" ");}
Serial.println(i2c_readLastByte());
i2c_stop(); 
}

Первый фрейм пишется и читается ОК, а второй  - фигня какая-то.  В мониторе наблюдаю:

setup
loop
3224
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
Stop

В первую очередь я подозреваю неправильную адресацию фрейма.  Главное в даташите ничего про адресацию фреймов не сказано, ну я и подумал, что адрес следующего фрейма 0 (старший байт адреса памяти) 32 (младший байт адреса).   Подскажите, кто с проблемой такой сталкивался!

 

5N62V
Offline
Зарегистрирован: 25.02.2016

Как оказалось, код был почти правильный. Проблема была не в адресации, а в том, что ЕЕПРОМка была недоступна, т.к., скорее всего,  не окончила записывать. Адресация страниц, как я и думал, 1ая - адрес 0, вторая - 32, третья 64... Младший байт адресации имеет 8 бит, старший - только 4 младших бита.

Добавил делеи перед обращением к шине ай2си , и все заработало стабильно.

#define EEPROM_ADDR_WRITE 0b10100000
#define EEPROM_ADDR_READ 0b10100001

unsigned int counter = 0;
byte buffer[128] = {0};

void setup() {
Serial.begin(9600);
TWBR = 72; // i2c 100kHz, CPU 16MHz
 Serial.println("setup");
 for(uint8_t i = 0; i<128; i++)buffer[i] = i;
}

void loop() {
  Serial.println("loop");
  uint32_t time = micros();

i2c_writePage(0,0,&buffer[0],32);//записываем первые 32байта
delay(2);
i2c_writePage(0,32,&buffer[32],32);//записываем вторые 32байта
delay(2);
i2c_writePage(0,64,&buffer[64],32);//записываем третьи 32байта
delay(2);
Serial.println();
i2c_readPage(0,0,32); //читаем первые 32байта
delay(2);
i2c_readPage(0,32,32);//читаем вторые 32байта
delay(2);
i2c_readPage(0,64,32);//читаем третьи 32байта

Serial.println("stop");
while(1) ;

}


 void i2c_start(){
TWCR = 0b10100100;// TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN)
while(! (TWCR&(1<<7)));
}

void i2c_stop(){
TWCR = 0b10010100;// TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN)  
}

void i2c_sendByte (unsigned char b){
  TWDR = b;
  TWCR = 0b10000100; //TWCR = (1<<TWINT)|(1<<TWEN)
  while(! (TWCR&(1<<7)));
}

uint8_t i2c_readByte(){
 // TWCR = 0b11000100;// TWCR = (1<<TWINT)|(1<<TWEA)|(1<<TWEN);
 TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWEA);
  while(! (TWCR&(1<<7)));
  return TWDR;
}

uint8_t i2c_readLastByte(){
  TWCR = 0b10000100;// TWCR = (1<<TWINT)|(1<<TWEN);
  while(! (TWCR&(1<<7)));
  return TWDR;
}


void i2c_writePage(uint8_t a, uint8_t b, uint8_t * buf, uint8_t len){
i2c_start();
i2c_sendByte(EEPROM_ADDR_WRITE);
i2c_sendByte(a);
i2c_sendByte(b);
  for (uint8_t i = 0; i<len; i++) {
    i2c_sendByte(*(buf+i));

    }
i2c_stop();  
}

void i2c_readPage(uint8_t a, uint8_t b, uint8_t len){
  uint8_t zzz;
i2c_start();
i2c_sendByte(EEPROM_ADDR_WRITE);
i2c_sendByte(a);
i2c_sendByte(b); 
i2c_start();
i2c_sendByte(EEPROM_ADDR_READ);
for(uint8_t i = 0;i<len-1;i++) {Serial.print(i2c_readByte()); Serial.print(" ");}
Serial.println(i2c_readLastByte());
i2c_stop(); 
}

Потом уже обнаружил, что такой же вопрос уже подымался на этом форуме, но остался без ответа.

Монитор:

setup
loop
 
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
stop
 

Одно плохо: для записи 32х байт тратить 5мсек - чрезчур жирно ...

sadman41
Offline
Зарегистрирован: 19.10.2016

Byte Write: A Write operation requires two 8-bit data word addresses following the device address word and acknowledgment. Upon receipt of this address, the EEPROM will again respond with a zero then clock in the first 8-bit data word. Following receipt of the 8-bit data word, the EEPROM will output a zero. The addressing device, such as a microcontroller, must then terminate the write sequence with a Stop condition. At this time, the EEPROM enters an internally-timed Write cycle, tWR, to the nonvolatile memory (See Figure 6-5). All inputs are disabled during this Write cycle and the EEPROM will not respond until the Write is complete (See Figure 8-1).

Delay надо делать после записи каждой страницы - это так. Но ведь вместо delay() можно воткнуть полезное мигание светодиодом, например. Читать же можно не страницами, а тупо блоком на длину массива.

Гораздо интереснее, если писать этот код на Wire.h (поэкспериментировал - думал, что проще будет ;). С ним, например, в дефолтовом состоянии невозможно записать 32 байта. 30 - пожалуйста. Почему? Потому что мы сначала делаем write() двух байт стартовой ячейки, потом данные пишем. Но пишем-то не в устройство, а в буфер Wire. А потом только отправляем по endTransmission(). Т.е. данных мы может послать только 30 байта (если не править Wire.h), а следовательно - в кажной странице EEPROM будет неиспользуемая дырка в конце.  Но этого как бы мало - если мы попросим у requestFrom() больше, чем размер буфера, а потом начнем читать, то нам будут отдавать ожидаемые... 0xFF. Всегда. Пока не начнем просить меньше.  

Нечестный этот Си, ох нечестный...

5N62V
Offline
Зарегистрирован: 25.02.2016

sadman41 пишет:

Читать же можно не страницами, а тупо блоком на длину массива.

Я так понял, что можно даже всю еепромку за раз вычитать, если, например, данные сразу в сериал скидывать.

sadman41 пишет:
Гораздо интереснее, если писать этот код на Wire.h (поэкспериментировал - думал, что проще будет ;).
Ага, жесть! Я пробовал размер буфера в Wire.h увеличить - столкнулся с непропорциональным расходованием памяти. :) Ну собсно поэтому я и отказался от этой библиотеки. А тут универсальный код: сколько хош отправлять можно, например на еепромки с размером страницы 128 байт. Можно легко скорость шины переключать, правда пока не знаю можно ли  на практике так делать, при обращении к разным устройствам включать разную (максимально поддерживаемую) скорость.

sadman41
Offline
Зарегистрирован: 19.10.2016

5N62V пишет:

sadman41 пишет:

Читать же можно не страницами, а тупо блоком на длину массива.

Я так понял, что можно даже всю еепромку за раз вычитать, если, например, данные сразу в сериал скидывать.

Если в буфер скидывать, то да. А в Serial... не знаю, он еще том монстр. Может бsть начнутся затыки и входящие с i2c данные порастеряются.

Код у вас, полагаю, менее подвержен всяким зависимостям от буферов, но больно уж непонятный. Я поэтому и решил на Wire.h глянуть, что и как. По ячейке-то читаю, а вот со страничной записью не было нужды связываться.

Я жду FRAM для экспериментов - в ней, пишут, запись без задержек идет, на скорости интерфейса.

5N62V
Offline
Зарегистрирован: 25.02.2016

sadman41 пишет:

Код у вас, полагаю, менее подвержен всяким зависимостям от буферов, но больно уж непонятный.

Простой до безобразия, и удобен для работы с разными устройствами, формат у которых описан в даташите.  Если МК мастер, то используются в разной последовательности всего 5 функций : 

- условие старта

- условие стопа

-записать байт

-считать байт

-считать последний байт

И просто комбинируя этими пятью функциями удобно строить нужный формат, как сделано у меня в функциях i2c_readPage  и i2c_writePage.   Только по-хорошему нужно еще таймауты прописать, а то можно всю прогу подвесить из-за отвалившегося устройства.

 

sadman41 пишет:

Я жду FRAM для экспериментов - в ней, пишут, запись без задержек идет, на скорости интерфейса.

Я уже тоже хочу поиграться :)

sadman41
Offline
Зарегистрирован: 19.10.2016

5N62V пишет:

sadman41 пишет:

Я жду FRAM для экспериментов - в ней, пишут, запись без задержек идет, на скорости интерфейса.

Я уже тоже хочу поиграться :)

Если вы в столицах и любите паять, а не ждать, то берите на electronshik.ru. Судя по каталогу - там есть (FM24C04).

5N62V
Offline
Зарегистрирован: 25.02.2016

sadman41 пишет:

Если вы в столицах и любите паять, а не ждать, то берите на electronshik.ru. Судя по каталогу - там есть (FM24C04).

Эх, играться так играться: FM24V10

sadman41
Offline
Зарегистрирован: 19.10.2016

Не пролетите с питанием. дешевые и ёмкие - низковольтные.

sadman41
Offline
Зарегистрирован: 19.10.2016

Накатал еще один вариант, с SoftwareWire. Нельзя сказать, что библиотека летает, как самолет, но вешается на любые две ноги и использует только один внутренний буфер (32 байта) +имеет таймаут на операции (судя по коду). Почти совместима по синтаксису с обычной Wire. При небольшом хачинге (поэтому подключена из src) допускает переконфигурирование на лету - один глобальный объект на несколько пар SDA/SCL, естественно по-очереди.

#include "src\SoftwareWire\SoftwareWire.h"
SoftwareWire SoftTWI(SDA, SCL);

#define AT24C (0x56)

uint8_t AT24CXXSequentialWrite(const uint8_t _i2cAddress, uint16_t _startCellAddress, const uint8_t _lenght, uint8_t* _src) {
  uint8_t written;
  SoftTWI.beginTransmission(_i2cAddress);
  SoftTWI.write(highByte(_startCellAddress));
  SoftTWI.write(lowByte(_startCellAddress));
  written = SoftTWI.write(_src, _lenght);
  SoftTWI.endTransmission(true);
  if (written == _lenght) {
    delay(5); // tWR is 5ms max
  }
  return written;
}

uint8_t AT24CXXSequentialRead(const uint8_t _i2cAddress, const uint16_t _startCellAddress, const uint8_t _lenght, uint8_t* _dst) {
  uint8_t recieved;
  SoftTWI.beginTransmission(_i2cAddress);
  SoftTWI.write(highByte(_startCellAddress));
  SoftTWI.write(lowByte(_startCellAddress));
  SoftTWI.endTransmission(false);
  recieved = SoftTWI.requestFrom(_i2cAddress, _lenght, true); //  true - sendStop
  if (recieved == _lenght) {
    SoftTWI.readBytes(_dst, _lenght);
  }
    return recieved;
}

void setup() {
  uint8_t buffer[128];
  SoftTWI.begin();

  Serial.begin(9600);
  Serial.println("Prepare & write:");

  for (uint8_t i = 0; i < 64; i++) {
    buffer[i] = i + 0x10;
    Serial.print(buffer[i]); Serial.print(" ");
  }
  Serial.println();

  AT24CXXSequentialWrite(AT24C, 0, 32, &buffer[0]);
  AT24CXXSequentialWrite(AT24C, 32, 32, &buffer[32]);
  Serial.println("--------------------\nRead:");

  AT24CXXSequentialRead(AT24C, 0, 32, &buffer[64]);
  AT24CXXSequentialRead(AT24C, 32, 32, &buffer[96]);

  for (uint8_t i = 64; i < 128; i++) {
    Serial.print(buffer[i]); Serial.print(" ");
  }
  Serial.println();

}

void loop() {
  // put your main code here, to run repeatedly:

}