Работа с eeprom m24c02 по i2c

Работа с энергонезависимой памятью в Arduino (EEPROM) – 2150692.ru

Микроконтроллеры Atmega328, работающие в Arduino UNO и NANO имеют на борту 1024 байт EEPROM – энергонезависимой памяти, в которой можно сохранять какие-либо данные, которые будут доступны после отключения питания.

Это может пригодиться для хранения каких-нибудь данных или значений.

Для работы с данной памятью в составе Arduino IDE уже есть удобная библиотека EEPROM (hardwarelibrariesEEPROM).

byte EEPROM.read(address)

Описание:

Считывает байт из энергонезависимой памяти EEPROM. Если байт до этого никогда не перезаписывался – вернёт значение 255.

Параметры:

address: порядковый номер ячейки памяти для чтения — от 0 до 511 (int)

Возвращаемое значение:

Байт, хранимый в ячейке памяти.

Пример (File-Examples-EEPROM-eeprom_read):

/* * Чтение EEPROM * * Считывает значения всех байтов энергонезависимой памяти * EEPROM и выводит их в COM-порт */ #include // начальный адрес памяти EEPROM int address = 0; byte value; void setup() {  Serial.begin(9600); } void loop() {  // считываем значение по текущему адресу EEPROM  value = EEPROM.read(address);  Serial.print(address);  Serial.print(” “);  Serial.print(value, DEC);  Serial.println();  // устанавливаем следующую ячейку памяти  address = address + 1;  // EEPROM содержит всего 512 байт: от 0 до 511, поэтому  // если адрес достиг 512, то снова переходим на 0  if (address == 512)    address = 0;  delay(500); }

void EEPROM.write(address, value)

Описание:

Записывает байт в энергонезависимую память

Параметры:

address: порядковый номер ячейки памяти для записи — от 0 до 511 (int)
value: байт для записи – от 0 до 255 (byte)

Возвращаемое значение:

ничего

Примечание:

Документация (datasheet) на микроконтроллеры Atmega8/168 говорит, что возможное количество циклов перезаписи данных в памяти ограничено 100000 раз (Write/Erase Cycles). Это следует учитывать при использовании данной памяти.

Так же документация указывает, что время, требуемое для завершения цикла записи составляет 3.3 ms.

Если в это время попытаться что-либо считать/записать в EEPROM, то такая попытка окончится неудачей 🙁
Однако, данная задержка уже учитывается библиотекой EEPROM, поэтому в дополнительном вызове delay() нет необходимости.

Пример (File-Examples-EEPROM-eeprom_write):

/*   * EEPROM Write   *   * Сохраняет в энергонезависимой памяти EEPROM значения,   * считанные с аналогового входа analog input 0.   * Данные значения останутся в памяти и после отключения питания   * от платы и в будущем могут быть доступны для   * другого скетча.  */ #include // текущее значение адреса EEPROM int addr = 0; void setup() { } void loop() {  // деление на 4 необходимо, чтобы перевести значение от  // 0-1023 к одному байту, т.к. EEPROM может хранить только  // значения от 0 до 255.  int val = analogRead(0) / 4;  // записываем значение в энергонезависимую память  EEPROM.write(addr, val);  // устанавливаем следующую ячейку памяти.  // т.к. EEPROM содержит всего 512 ячеек – при достижении  // конца памяти – возвращаемся на начало 🙂  addr = addr + 1;  if (addr == 512)    addr = 0;  delay(100); }

В примере eeprom_clear (File-Examples-EEPROM-eeprom_clear)
показано, как произвести очистку памяти – просто заполнить её нулями:

// записываем 0 во все 512 байт памяти EEPROM for (int i = 0; i > 0) & 0xFF);  byte highByte = ((p_value >> 8) & 0xFF);  EEPROM.write(p_address, lowByte);  EEPROM.write(p_address + 1, highByte); } //Эта функция прочитает 2 байта типа integer из eeprom с адресов address и address + 1 unsigned int EEPROMReadInt(int p_address) {  byte lowByte = EEPROM.read(p_address);  byte highByte = EEPROM.read(p_address + 1);  return ((lowByte

Источник: http://www.2150692.ru/faq/38-arduino-eeprom

Proteus8.x + MSP430x2xx: программная реализация I2C интерфейса, подключение устройств: RTC DS1307/DS3231, EEPROM AT24C512/AT24C32

разделы: MSP430 , Proteus , I2C , RTC , дата: 5 апреля 2017г

Полтора года назад я уже бегло рассматривал протокол I2C, теперь же настало время изучить его более подробно.

Попробуем написать программную реализацию протокола, рассмотрим “подводные камни” такой реализации, а также способы отладки шины I2C. Для обкатки алгоритма попробуем подключить следующие устройства на шине I2C: RTC DS1307/DS3231 и EEPROM AT24C512/AT24C32.

Для проектирования будет использована CAD Proteus_8.5. Для проверки на реальном железе будет использован чип MSP430G2453.

Часы реального времени на микросхеме DS1307

Итак, тренироваться будем на RTC DS1307, но здесь должен заметить, что данный чип относиться к 5-вольтовой логике поэтому с 3.3-вольтовым MSP430 он работать не будет. Заставить работать данную связку можно только в Proteus. Для тестирования на реальном железе я буду использовать DS3231 модуль. Однако для того чтобы понять I2C не нужен готовый модуль, он будет только мешать.

Суть проблемы

Когда не очень опытный радиолюбитель берет устройство на I2C, ему надо его как-то проверить, убедиться что оно хотя бы в принципе работает. Как это сделать? Обычно делается это на Arduino (я по крайней мере так делаю). Находится какой-нибудь скетч для проверки, и на нем проверяется.

Однако, при наличии проблем, как узнать в чем кроется загвоздка: в “косячной” библиотеке или в железке? Например, если мы загрузим этот скетч для работы с DS1307, и запустим его БЕЗ всякого модуля, то получим такую картинку:

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

И здесь проблема здесь не в низком качестве кода скетча. Если посмотрим API References:

то увидим, что при установлении связи нам ничего не возвращается, в то время как по спецификации протокола, если устройство приняло свой адрес и готово к работе, оно посылает ответ – ACK.

Поэтому давайте отбросим Arduino в сторону и напишем свой код c шахматами и поэтессами с обработкой исключительных ситуаций.

Но прежде, чем что-то написать, следует заглянуть в руководство по DS1307:

В первую очередь нас будет интересовать принципиальная схема. На ней ничего сложного нет, только три подтягивающих резистора(красные стрелки), батарея для автономной работы, да кварц. Без батареи чип вполне будет работать, даже без кварца будет работать, часы конечно тогда не будут идти, но на чип вполне можно будет записывать и считывать с него.

Первые два подтягивающих резистора Rpu формируют шину I2C. Если посмотреть на схему китайского модуля(взято отсюда):

То здесь используются резисторы номиналом на 3.3K, но я встречал схемы где резисторы были и на 10K.

У меня на паечной макетке это получилось как-то так:

Если у вас получится лучше, запишите это себе в плюсы. Здесь слева – это чип EEPROM(о нем позже). Моя наверное главная ошибка – резонатор следовало повернуть на 90 градусов, сэкономилось бы место на плате.

Теперь в Proteus соберем следующую схему:

Здесь кроме чипа DS1307, часового кварца и гальванической батареи, также понадобится источник постоянного напряжения на 3.3 Вольт, и I2C отладчик.

Для освежения памяти, я скопировал из своего предыдущего поста диаграмму протокола I2C и его основные положения:

показать

Waveform showing the clock and data timing for an I2C message.

    Основные моменты:

  1. Есть две линии: SCL служит для тактирования, SDA служит для передачи данных.
  2. Скважность импульсов не играет роли. Один такт может длиться 5мкс, другой 5мс.
  3. Высокий уровень формируется pull-up режимом, низкий – земля.
  4. Данные передаются сессиями.
  5. Каждая отдельная сессия работает только на прием или только на передачу.
  6. В начале и в конце сессии соответственно формируются сигналы START и STOP. Между этими сигналами линия считается занятой.
  7. Только при START и STOP, SDA линия меняет уровень при высоком уровне SCL.
  8. START формируется последовательностью: a) падением SDA при высоком уровне SCL, б) падением SCL.
  9. STOP формируется последовательностью: a) поднятием SCL при низком SDA, б) поднятием SDA при высоком SCL.
  10. При передаче данных уровень SDA меняется только при низком SCL.
  11. При чтении данных, SDA уровень считывается только при высоком SCL.
  12. После формирования START, передается адрес устройства, с которым хотят связаться. Младший бит адреса указывает режим сессии.
  13. Младший бит равный нулю в адресе устройства означает, что сессия будет направлена на запись, т.е. передачу в адресуемое устройство.
  14. Младший бит равный единице в адресе устройства означает, что сессия будет направлена на чтение, т.е. будет происходить чтение с адресуемого устройства.
  15. Байты передаются побитно, начиная от старшего разряда к младшему.
  16. После передачи каждого байта, принимающая сторона должна формировать ответ, означающий, что передача прошла успешно: ACK.
  17. При чтении с устройства, не сформировав ответ, мы дадим понять устройству, что прием закончен, и мы получили все, что нужно. Это называется NACK.

Итак, работа с I2C идет сессиями. В руководстве представлены варианты допустимых сессий для записи и чтения:

Есть три варианта сессий: для записи, для чтения, и для чтения с определенного адреса.

Карта регистров DS1307:

Т.е. имеем 7 часовых регистра, 1 регистр конфигурации SQW и 56 байт ОЗУ где можно хранить свои данные.

Опрос I2C устройства

Самая простая успешная сессия для I2C шины, это передача адреса в режиме записи, с последующим завершением сессии. Т.е. это такой опрос, есть ли такое устройство на линии/на связи.

Взяв за основу программы программного UART-передатчика из предыдущего поста и программной реализации I2C для AVR из поста за 2015 год, (в основу которой в свою очередь лег код из Procyon avrLib,) я сочинил такой код:

#include
#include
#include #define LED BIT0
#define BTN BIT1 #define RXD BIT1
#define TXD BIT2
#define uOUT P1OUT
#define uDIR P1DIR #define SCL BIT0
#define SDA BIT1
#define sclOUT P2OUT
#define sdaOUT P2OUT
#define sclDIR P2DIR
#define sdaDIR P2DIR
#define sclREN P2REN
#define sdaREN P2REN
#define sdaIN P2IN
#define sclIN P2IN #define QDEL __delay_cycles(20);
#define HDEL __delay_cycles(40); #define SDA_I2C_HI sdaDIR&=~SDA
#define SCL_I2C_HI sclDIR&=~SCL #define SDA_I2C_LO sdaDIR|=SDA
#define SCL_I2C_LO sclDIR|=SCL #define SCL_TOGGLE_I2C HDEL; SCL_I2C_HI;HDEL;SCL_I2C_LO; #define DS1307_ADR 0xD0 volatile uint16_t count;
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer0(void){ count=0;
} static int start_i2c() { int ret=64; uint8_t valueSDA, valueSCL; sdaOUT &=~SDA; sclOUT &=~SCL; do { SDA_I2C_HI; SCL_I2C_HI; QDEL; valueSDA=sdaIN & SDA; valueSCL=sclIN & SCL; } while((!valueSDA || !valueSCL) && –ret>0); if (!ret) return 0; SDA_I2C_LO;QDEL; SCL_I2C_LO;QDEL; return ret;
} static int stop_i2c(){ SDA_I2C_LO; SCL_I2C_LO;HDEL; SCL_I2C_HI;QDEL; SDA_I2C_HI;QDEL; if (!(sdaIN & SDA) || !(sclIN & SCL)) return 1; else return 0;
} static uint8_t send_i2c(uint8_t value) { int bits=8; while(bits>0) { bits–; SCL_I2C_LO; QDEL; while((sclIN & SCL)); if (value & (10); if (!ret) return 0; SDA_I2C_LO;QDEL; SCL_I2C_LO;QDEL; return ret;
} static int stop_i2c(){ SDA_I2C_LO; SCL_I2C_LO;HDEL; SCL_I2C_HI;QDEL; SDA_I2C_HI;QDEL; if (!(sdaIN & SDA) || !(sclIN & SCL)) return 1; else return 0;
} static uint8_t send_i2c(uint8_t value) { int bits=8; while(bits>0) { bits–; SCL_I2C_LO; QDEL; while((sclIN & SCL)); if (value & (10); if (!ret) return 0; SDA_I2C_LO;QDEL; SCL_I2C_LO;QDEL; return ret;
} static int stop_i2c(){ SDA_I2C_LO; SCL_I2C_LO;HDEL; SCL_I2C_HI;QDEL; SDA_I2C_HI;QDEL; if (!(sdaIN & SDA) || !(sclIN & SCL)) return 1; else return 0;
} static uint8_t send_i2c(uint8_t value) { int bits=8; while(bits>0) { bits–; SCL_I2C_LO; QDEL; while((sclIN & SCL)); if (value & (14]); send_uart(symbol[bcd&0x0F]);
} static int ds1307_set_data() { int attempts,i; attempts=start_i2c(); if (attempts){ “OK, attempts: “65 i=0; uint8_t ack; do { ack=send_i2c(init[i]); } while (!ack && ++i0); if (!ret) return 0; SDA_I2C_LO;QDEL; SCL_I2C_LO;QDEL; return ret;
} static int stop_i2c(){ SDA_I2C_LO; SCL_I2C_LO;HDEL; SCL_I2C_HI;QDEL; SDA_I2C_HI;QDEL; if (!(sdaIN & SDA) || !(sclIN & SCL)) return 1; else return 0;
} static uint8_t send_i2c(uint8_t value) { int bits=8; while(bits>0) { bits–; SCL_I2C_LO; QDEL; while((sclIN & SCL)); if (value & (14]); send_uart(symbol[bcd&0x0F]);
} static int ds1307_set_data() { int attempts,i; attempts=start_i2c(); if (attempts){ “OK, attempts: “65 i=0; uint8_t ack; do { ack=send_i2c(init[i]); } while (!ack && ++i>1); sqn = sqn / a[trim]; int i; for (i = 0; i < dig; i++) { next_num += (sqn % (a[trim])) * (a[i]); sqn = sqn / 10; } return next_num; } static int start_i2c() { int ret=64; uint8_t valueSDA, valueSCL; sdaOUT &=~SDA; sclOUT &=~SCL; do { SDA_I2C_HI; SCL_I2C_HI; QDEL; valueSDA=sdaIN & SDA; valueSCL=sclIN & SCL; } while((!valueSDA || !valueSCL) && --ret>0); if (!ret) return 0; SDA_I2C_LO;QDEL; SCL_I2C_LO;QDEL; return ret;
} static int stop_i2c(){ SDA_I2C_LO; SCL_I2C_LO;HDEL; SCL_I2C_HI;QDEL; SDA_I2C_HI;QDEL; if (!(sdaIN & SDA) || !(sclIN & SCL)) return 1; else return 0;
} static uint8_t send_i2c(uint8_t value) { int bits=8; while(bits>0) { bits–; SCL_I2C_LO; QDEL; while((sclIN & SCL)); if (value & (1 128) return 0; at24_ard[1]=(uint8_t)(address>>8); at24_ard[2]=(uint8_t)(address&0x00FF); if (!at24c512_set_ard(0)) { send_string(“AT24C512 address setting was success!
“); } else { send_string(“ERROR address setting was failure!
” ); return 0; } uint8_t i=0; uint8_t ack; do { ack=send_i2c(at24_data[i]); __delay_cycles(10000); } while (!ack && ++i>8); at24_ard[2]=(uint8_t)(address&0x00FF); if (!at24c512_set_ard(1)) { send_string(“AT24C512 address setting was success!
“); } else { send_string(“ERROR address setting was failure!
” ); return 0; } for(i=0;i1); sqn = sqn / a[trim]; int i; for (i = 0; i < dig; i++) { next_num += (sqn % (a[trim])) * (a[i]); sqn = sqn / 10; } return next_num; } static int start_i2c() { int ret=64; uint8_t valueSDA, valueSCL; sdaOUT &=~SDA; sclOUT &=~SCL; do { SDA_I2C_HI; SCL_I2C_HI; QDEL; valueSDA=sdaIN & SDA; valueSCL=sclIN & SCL; } while((!valueSDA || !valueSCL) && --ret>0); if (!ret) return 0; SDA_I2C_LO;QDEL; SCL_I2C_LO;QDEL; return ret;
} static int stop_i2c(){ SDA_I2C_LO; SCL_I2C_LO;HDEL; SCL_I2C_HI;QDEL; SDA_I2C_HI;QDEL; if (!(sdaIN & SDA) || !(sclIN & SCL)) return 1; else return 0;
} static uint8_t send_i2c(uint8_t value) { int bits=8; while(bits>0) { bits–; SCL_I2C_LO; QDEL; while((sclIN & SCL)); if (value & (1 32) return 0; at24_ard[1]=(uint8_t)(address>>8); at24_ard[2]=(uint8_t)(address&0x00FF); if (!at24c512_set_ard(0)) { send_string(“AT24C32 address setting was success!
“); } else { send_string(“ERROR address setting was failure!
” ); return 0; } uint8_t i=0; uint8_t ack; do { ack=send_i2c(at24_data[i]); wait_ms(10); } while (!ack && ++i>8); at24_ard[2]=(uint8_t)(address&0x00FF); if (!at24c512_set_ard(1)) { send_string(“AT24C32 address setting was success!
“); } else { send_string(“ERROR address setting was failure!
” ); return 0; } for(i=0;i

Источник: http://www.count-zero.ru/2017/msp430i2c/

Учебный курс AVR. Работа с EEPROM. Объявление переменных. Чтение и запись данных. Ч1

Введение

   При программировании микроконтроллеров AVR иногда возникает потребность сохранять данные, которые бы после выключения питания или сброса контроллера не изменяли свое значение.

Для этих целей в составе AVR есть энергонезависимая память данных EEPROM (Electrically Erasable Programmable Read-Only Memory — электрически стираемое перепрограммируемое ПЗУ).

 
   EEPROM имеет адресное пространство отличное от адресных пространств ОЗУ и flash памяти, в котором можно читать и записывать одиночные байты. В зависимости от модели микроконтроллера EEPROM может иметь объем от 512 байт (как, например, в микроконтроллере atmega16) до нескольких килобайт.

Читайте также:  Изготовление и ввод в эксплуатацию электронных систем

Гарантированное количество циклов перезаписи этой памяти составляет не меньше 100000. 
   В этой статье на примере atmega16 мы разберемся, как работать с этим типом памяти, какие возможные проблемы при этом могут возникать и как с ними бороться.

Объявление переменных в EEPROM

   Использование EEPROM начинается с объявления переменных, хранящиеся в этой памяти. Синтаксис объявления таких переменных отличается от объявлений обычных переменных (хранящихся в ОЗУ) только наличием ключевого слова. В зависимости от компилятора данное ключевое слово может разным. 

Объявление переменной в EEPROM для IAR AVR и CodeVision AVR://8-ми разрядная переменная__eeprom uint8_t setting_2//объявление массива 16-ти разрядных переменных __eeprom uint16_t set[8];//объявление массива и его инициализация__eeprom uint8_t data[3] = {11, 3, 13};//указатель на 8-ми разрядную переменную в EEPROM, который сам хранится в RAMuint8_t __eeprom * pSetting;//указатель на 8-ми разрядную переменную в EEPROM, который сам храниться в EEPROM__eeprom uint8_t __eeprom*pSetting;

Объявление переменной в EEPROM для AtmelStudio 6: 
#include

//8-ми разрядная переменная в EEPROM
uint8_t setting EEMEM;//объявление массива в EEPROM
uint16_t set[8] EEMEM;//указатель на 8-ми разрядную переменную в EEPROM, который сам хранится в RAM
uint8_t *pSetting;//указатель на 8-ми разрядную переменную в EEPROM, который сам храниться в EEPROM
uint8_t *pSetting EEMEM;

Инициализация переменных в EEPROM

   При объявлении переменных в EEPROM их можно инициализировать, то есть присвоить начальные значения. 

//для IAR AVR, CodeVision AVR__eeprom uint8_t counter = 100;__eeprom uint16_t data[3] = {20, 08, 1981};//для AtmelStudio 6uint8_t counter EEMEM = 23;

uint8_t data[3] EEMEM = {21, 04, 1979};

   Инициализацию переменных хранящихся в ОЗУ компилятор “запихивает” в начало программы микроконтроллера – перед вызовом функции main. И она выполняется каждый раз, когда на микроконтроллер подается питание или происходит его сброс. 
   С EEPROM переменными ситуация немного другая, их должен инициализировать сам пользователь путем программирования EEPROM специальным файлом (с расширением .eep). 
  Как сгенерировать файл для EEPROM? Если в коде есть инициализация EEPROM переменных, то AtmelStudio 6 и CodeVision AVR создадут этот файл автоматически. А вот в IAR`e для этого нужно прописывать линкеру команды. Делается это так.
   

Меню Project > Options…>Linker вкладка Output. Устанавливаем значения по умолчанию – галочка Override default снята, формат – Debug information for C-SPY. На вкладке Extra Options ставим галочку Use Command Options и в окошке ниже прописываем такие строчки:-Ointel-standard,(CODE)=.hex
-Ointel-standard,(XDATA)=.eep

   После компиляции и сборки проекта IAR создаст файл прошивки – .hex и файл для EEPROM`a – .eep 

   Полученный eep файл записывается с помощью программатора в микроконтроллер.

Чтение и запись EEPROM

   В IAR`e и CodeVision AVR использование EEPROM переменных по сути ничем не отличается от использования обычных переменных (хранящихся в ОЗУ и регистрах).

Вся работа по организации чтения и записи в эти EEPROM переменные выполняется компилятором.//для IAR AVR, CodeVision AVR__eeprom uint8_t data;…

//читаем из EEPROMuint8_t tmp = data;//записываем в EEPROM

data = 129;

   В AtmelStudio для чтения/записи EEPROM переменных используются специальные макросы. Они определены в файле eeprom.h. Вот некоторые из них:
uint8_t eeprom_read_byte (const uint8_t *__p) – прочитать байт 
uint16_t eeprom_read_word (const uint16_t *__p) – прочитать слово (2 байта)
uint32_t eeprom_read_dword (const uint32_t *__p) – прочитать двойное слово (4 байта)void eeprom_write_byte (uint8_t *__p, uint8_t __value) – запись байта
void eeprom_write_word (uint16_t *__p, uint16_t __value) – запись слова (2 байта)
void eeprom_write_dword (uint32_t *__p, uint32_t __value) – запись дв. слова (4 байта)   Макросы принимают в качестве параметра адрес переменной размещенной в EEPROM. Для взятия адреса переменной используется оператор &. Примеры ниже поясняют использование этих макросов. 

#include //объявляем переменные uint8_t data EEMEM;uint16_t pin_code EEMEM;…//читаем байт из eepromuint8_t tmp = eeprom_read_byte(&data);//записываем слово в eepromeeprom_write_word (&pin_code, 5327)

Заключение

Из этой статьи вы узнали:- как объявить переменную в EEPROM памяти,- как создать файл для инициализации EEPROM,- как прочитать и записать данные в EEPROM.

   В следующей статье будут разобраны регистры, используемые для чтения и записи в EEPROM, а также низкоуровневая работа с этим типом памяти.

Источник: http://chipenable.ru/index.php/programming-avr/item/158

FRAM через I2C для Arduino как замена EEPROM

Продолжу рассказывать про приборную панель для мотоцикла. Это замечательное устройство содержит одометр, то есть, счётчик пройденного пути в километрах, а у того есть плохое свойство — он должен сохранять данные и при выключенном питании. Ой, ещё есть моточасы, их тоже надо хранить как-то энергозависимо. Внутри Ардуины есть EEPROM, конечно же.

Много места не надо, чтобы хранить пяток длинных целых, но есть нюанс. EEPROM имеет слишком ограниченный ресурс на запись. Хотелось бы писать данные раз в несколько секунд хотя бы. Ресурс же EEPROM позволяет это делать вполне обозримое время, то есть, встроенная память явно не вечна.

Сначала я хотел обмануть судьбу записывая структурку данных в разные места 1К памяти чипа по кругу. Упёрся в то, что указатель надо где-то хранить тоже, а данные достаточно случайные, чтобы использовать какой-то маркер для последовательного поиска. Необходимость хранения указателя можно обмануть разными способами.

Например так:struct MarkedSavedData { byte marker; // показывает, занято место или нет. struct SavedData { // Собственно данные для сохранения }
} data;
Структуркой MarkedSavedData заполняется eerpom или флеш или что-то по кругу. Чтобы не писать указатель, в свободных записях делаем data.marker=0x00, а в занятой текущей data.marker=0xff, например.

В процессе работы, конечно же, запись идёт по указателям, а при старте контроллера просто поиском по всей памяти ищется структура с data.marker==0xff — это последние правильные данные. Плохо, что каждый раз две записи получаются тк надо обнулить data.marker освобождаемой записи. Есть вариант с последовательным счётчиком.

struct MarkedSavedData { unsugned int counter; // последовательный номер записи. struct SavedData { // Собственно данные для сохранения }
} data;
На каждую запись увеличивать счётчик на единицу забив на переполнение.

При старте контроллера искать самый большой счётчик с учётом возможного переполнения, что не так уж и трудно, а сэкономить sram можно сделав для этого функцию и поместив промежуточные структурки в стеке в локальных переменных её. Всё это хорошо, но это припарки.

Коллеги из НТЦ Метротек подсказали поискать FRAM. Это ферритовая память с бешеным быстродействием и 1014 циклами записи.

Услужливый Aliexpress привёз мне вот такой модуль. Память в виде модуля дорогая весьма, кстати. Сам же чип стоит 16р/шт. В микросхеме 512 байт, то есть, вроде и немного, но с учётом бесконечному числу записей вполне достаточно. Погуглив на тему готового чего-то для этого чипа я не нашёл ничего. Отличная кошка, решил я, буду на ней тренироваться! Открыл доку по Wire, даташит по FM24, чей-то проект EEPROM/I2C с похожим интерфейсом и набросал класс для FRAM.

Проект на гитхабе: github.com/nw-wind/FM24I2C

Пример прилагается вот такой.#include “FM24I2C.h” // Объект для платы. Адрес в i2c.
FM24I2C fm(0x57); void setup() { Wire.begin(); Serial.begin(9600); char str1[]=”12345678901234567890″; char str2[]=”qwertyuiopasdfghjklzxcvbnm”; int a1=0x00; // Первый в FRAM int a2=0x40; // Второй адрес в FRAM fm.pack(a1,str1,strlen(str1)+1); // Пишем в память delay(5); fm.

pack(a2,str2,strlen(str2)+1); // Пишем вторую строку delay(5); char buf[80]; fm.unpack(a2,buf,strlen(str2)+1); // Читаем вторую Serial.println(str2); fm.unpack(a1,buf,strlen(str1)+1); // Читаем первую Serial.println(str1);
}
Протокол i2c для FRAM сильно проще, чем для EEPROM. Память работает быстрее передачи данных по шине и можно лить хоть все 2К ардуининых мозгов за один раз.

Польза от моего кода хоть в том, что нет лишнего разбиения на блоки по 32 байта или вообще побайтной передачи.class FM24I2C { private: int id; public: FM24I2C(int id_addr); ~FM24I2C(); void pack(int addr, void* data, int len); // Упаковать данные в FRAM int unpack(int addr, void* data, int len); // Распаковать из FRAM. Возвращает количество переданных байтов.

// Это уже специально для меня, пишет беззнаковые длинные целые. void inline writeUnsignedLong(int addr, unsigned long data) { pack(addr, (void*)&data, sizeof(unsigned long)); } // И читает.

unsigned long inline readUnsignedLong(int addr) { unsigned long data; return unpack(addr, (void*)&data, sizeof(unsigned long)) == sizeof(unsigned long) ? data : 0UL; } // Можно для других типов сделать чтение/запись, но мне было не нужно, а флеш занимает. // Каждый же может унаследовать класс и дописать сам.
};
Кода же немножко совсем.void FM24I2C::pack(int addr, void* data, int len) { Wire.

beginTransmission(id); Wire.write((byte*)&addr,2); Wire.write((byte*)data,len); // Наверное, стоит всё же unsigned int использовать 🙂 Wire.endTransmission(true);
} int FM24I2C::unpack(int addr, void* data, int len) { int rc; byte *p; Wire.beginTransmission(id); Wire.write((byte*)&addr,2); Wire.endTransmission(false); Wire.

requestFrom(id,len); // Здесь можно поспорить про замену rc на p-data 🙂 for (rc=0, p=(byte*)data; Wire.available() && rc < len; rc++, p++) { *p=Wire.read(); } return(rc); }
Так как на модуле, кроме чипа и разъёмов, практически ничего нет, уже хочу купить несколько микросхем. Нравятся.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Источник: https://habr.com/post/319336/

База знаний по Pro/Engineer, Creo, Solidworks, электронике на STM32

В этом уроке по STM32 вы научитесь:

  • подключать EEPROM память
  • конфигурировать шину I2C в CubeMX
  • записывать и считывать байт с EEPROM памяти

Итак, рассмотрим подключение к оценочной плате STM32VLDISCOVERY (STM32F100RBT6B) энергонезависимой EEPROM памяти типа 24AA02/24LC02B c двумя килобитами на борту (даташит на микросхему 24AA02-24LC02B.pdf и файлы примера программы прилагаются).

EEPROM память позволяет сохранить значения переменных или другие данные при аварийном отключении питания. Обычно блок питания микроконтроллера содержит достаточно энергии (в конденсаторах) для того, чтобы успеть сохранить в память важные данные.

Подключение EEPROM памяти

В соответствии с описанием эта EEPROM память подключается по шине I2C, используя линии связи SDA (линия данных) и SCL (линия тактирования).

Эти линии нужно обязательно подтянуть к напряжению питания резисторами. В нашем случае будем использовать резисторы номиналом 10 кОм (по даташиту 10 кОм используются на частоте тактирования 100 кГц, а 2 кОм используются на частоте тактирования 400 кГц). Вывод WP (Write-Protect Input) подключим к земле для включения режима записи.

Адресные выводы А0-А1-А2 тоже подключим к земле, хотя это и не обязательно, т.к. в этой микросхеме они никуда не подключаются внутри. Они обычно используются для изменения адреса этого устройства в шине I2C (как например, в микросхеме типа 24AA256/24LC256/24FC256).

Как видно из даташита, адрес устройства у нас получается из следующих бит (в шине I2C в большинстве случаев используется семибитная адресация): 1010ХХХ – где ХХХ = A0-A1-A2, а нашем случае это 1010000 (0xA0 h), т.к. формально  мы выводы A0-A1-A2 подключили к земле (к нулю).

Запомним это число.

Конфигурирование шины I2C в CubeMX

Итак, со схемой разобрались – перейдем теперь в CubeMX, где выбираем тип нашего микроконтроллера STM32F100RBT6B. На вкладке Pinout в разделе Peripherals в модуле I2C1 выбираем режим шины I2C.

Как видим CubeMX автоматически задействовал порты B6 и B7, согласно даташиту на микроконтроллер.

Порт PC8 (подключен к синему светодиоду) настроим на выход для сигнализации о нормальной записи в микросхему.

Перейдем на вкладку Configuration и проверим конфигурацию модуля I2C1. Режим скорости выбран стандартным Standard Mode – максимум 100 кГц, что соответствует номиналу резисторов 10 кОм.

CubeMX позволяет выбрать также режим Fast Mode – до 400 кГц. В этом случае нужно было бы применять резисторы 2 кОм. По даташиту, наша микросхема допускает эту частоту работы при напряжении питания выше 2.5 В.

Остальные настройки оставим без изменений и сгенерируем код, например для KEIL. В папке Src появился основной файл main.c, вспомогательный файл конфигурирования периферии stm32f1xx_hal_msp.

c и файл прерываний stm32f1xx_it.c. Прерывания мы не использовали, поэтому файл stm32f1xx_it.c нас не интересует. В файле stm32f1xx_hal_msp.

c можно увидеть инициализацию портов B6 и B7 под шину I2C с помощью функций HAL:

/* stm32f1xx_hal_msp.c */ void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c) { GPIO_InitTypeDef GPIO_InitStruct; if(hi2c->Instance==I2C1) { /* USER CODE BEGIN I2C1_MspInit 0 */ /* USER CODE END I2C1_MspInit 0 */ /**I2C1 GPIO Configuration PB6 ——> I2C1_SCL PB7 ——> I2C1_SDA */ GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); /* Peripheral clock enable */ __I2C1_CLK_ENABLE(); /* USER CODE BEGIN I2C1_MspInit 1 */ /* USER CODE END I2C1_MspInit 1 */ }

В файле main.c конфигуриуется сама шина I2C, согласно нашим установкам в CubeMX:

/* main.c */ void MX_I2C1_Init(void) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLED; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLED; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLED; HAL_I2C_Init(&hi2c1); }

Кстати, иногда CubeMX  глючит и не подключает модуль I2C, и это естественно вызывает кучу ошибок при компиляции. Для ручного включения этого модуля придется открыть файл Incstm32f1xx_hal_conf.h (это для 100 серии микроконтроллера) и в нем раскомментировать строку #define HAL_I2C_MODULE_ENABLED.

Запись и чтение байта с EEPROM памяти

Инициализация других объектов нас сейчас мало интересует, поэтому переходим сразу к нашей задаче. В разделе пользовательских переменных инициализируем нужные нам объекты: однобайтный буфер обмена xBuffer, адрес EEPROM микросхемы I2C1_DEVICE_ADDRESS = 0x50 по шине I2C и ячейку памяти MEMORY_ADDRESS с адресом 0x08.

/* main.c */ /* USER CODE BEGIN PV */ /* Private variables ———————————————————*/ uint8_t xBuffer[1]; #define I2C1_DEVICE_ADDRESS 0x50 /* A0 = A1 = A2 = 0 */ #define MEMORY_ADDRESS 0x08 /* USER CODE END PV */

Важно помнить, что запись своего кода всегда нужно осуществлять в строго отведенных блоках.

/* main.c */ /* USER CODE BEGIN…*/ /* USER CODE END…*/

Тем самым, при следующей генерации кода CubeMX не сотрет эти блоки. Теперь перейдем к записи и чтению одного байта из памяти. В бесконечном цикле main запишем:

/* main.c */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ xBuffer[0] = 'M'; //0x4D HAL_I2C_Mem_Write(&hi2c1, (uint16_t) I2C1_DEVICE_ADDRESS

Источник: http://engio.ru/index/vstraivaemyie-sistemyi/podklyuchaem-k-stm32-energonezavisimuyu-eeprom-pamyat.html

Работа с внешней EEPROM в Bascom-AVR на примере 24lc08

 Когда нужно длительное время хранить какие-нибудь рабочие данные, не боясь их потерять, используют штатную, встроенную в микроконтроллер, EEPROM память. Обычно размер этой памяти не велик и его хватает только для хранения каких-то небольших по объему данных, например пользовательских настроек или т.п.

Читайте также:  Индикатор напряжения для мопеда

А если нужно хранить данные размером десяток килобайт, то понятно что встроенной памятью не обойтись и нужно подключать внешнее устройство хранения. И тут как нельзя лучше подходят внешние микросхемы EEPROM. Например микросхемы из серии 24LCxx от компании Microchip.

Эти микросхемы поддерживают подключение по протоколу I2C и умеют хранить от 512 байт до 128 килобайт данных. К тому же, старшие модели могут работать в связке из себе подобных, таким образом размер памяти может быть увеличен за счет присвоения микросхемам памяти индивидуального адреса на I2C шине.

В младших же моделях, все микросхемы имеют фиксированный адрес 1010.

 В номенклатуре Microchip серии 24LC числовое значение после буквенного индекса  обозначает объем памяти в килобитах. Так, подопытная микросхема 24LC08 имеет на борту 8 килобит пространства под хранение данных (или 1 килобайт). 

 Подключение микросхемы 

  Данная микросхема выпускается в различных корпусах: DIP, SOIC, TSOP, DFN. Для каждого восьминогового типа корпуса сохраняется распиновка контактов. 

 Такое же расположение контактов имеют микросхемы старших моделей, поэтому они запросто могут быть заменены в случае нехватки ресурсов, без изменения типологии платы.

 Назначение выводов микросхемы смотрим ниже:

A0, A1, A2 – в данной микросхеме не используются, в старших моделях они служат для присвоения микросхеме индивидуального адреса на I2C шине.

SDA – линия данных 

SCL – линия тактовых импульсов 

WP – защита от записи. Если на данный вывод подан логический 0, то запись в память разрешена. Если подать логическую единицу, то возможно только чтение из памяти.  

Vcc – питание микросхемы. Напряжение может быть в пределах от 2.5 вольта до 5.5 вольта.

Vss – земля. 

 К микроконтроллеру микросхема подключаются следуя традициям протокола I2C, тоесть сигнальные линии подтягиваются к шине питания через резисторы номиналом 4,7к. Неиспользуемые выводы A0, A1, A2 можно посадить на землю (IC1 в примере Attiny2313)

 Организация памяти

 Для того чтобы понять принцип работы с микросхемами памяти, нужно разобраться как происходит адресация внутри микросхемы. В подопытной микросхеме 24LC08 все пространство памяти поделено на 4 блока по 256 байт в каждом блоке. Каждый блок имеет свой адрес.

 При обращении к микросхеме ведущее устройство (микроконтроллер) отправляет адрес устройства (он у нас фиксированный 1010) и адрес блока с которым нужно работать.

Затем отправляется адрес ячейки в которую нужно записать/прочитать данные. Что нужно сделать с данными – прочитать или записать – зависит от бита в конце посылки. Разберем на примерах.

 Запись данных  

 Для записи в микросхему одного байта, нужно выполнить следующую последовательность действий:

  1. Сконфигурировать интерфейс I2C
  2. Отослать стартовый бит
  3. Отослать адрес микросхемы + адрес блока памяти. В конце посылки должен стоять 0 (бит записи)
  4. Отослать адрес ячейки памяти в которую будет производится запись
  5. Отослать байт данных
  6. Отослать стоповый бит

 К примеру запишем один байт &hFF в первую ячейку памяти первого блока (адрес блока &b000, адрес ячейки &h00).

$regfile = “2313def.dat”
$crystal = 1000000

'конфигурируем scl и sda пины
Config Sda = Portb.7                           'I2C Data
Config Scl = Portb.

6                           'I2C Clock

Wait 1

'работа с микросхемой
I2cstart                                   'даем сигнал старт i2c шине
I2cwbyte &B10100000                        'отправляем адрес микросхемы и адрес блока
I2cwbyte &H00                              'отправляем адрес ячейки
I2cwbyte &HFF                              'отправляем байт, который нужно записать
I2cstop                                    'останавливаем работу i2c

End

 Постраничная запись

Для увеличения скорости записи данных существует метод постраничной записи. Одна страница – это область из 16 байт (один столбец на картинке выше).

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

Для записи одной страницы отправляем адрес первой ячейки и затем 16 раз отправляем необходимые данные, причем если отправить 17 байт, то последний байт перезапишет первый и т.д. Для примера запишем первую страницу первого блока. Адрес первой ячейки 
&h00.

$regfile = “2313def.dat”
$crystal = 1000000

'конфигурируем scl и sda пины
Config Sda = Portb.7                     'I2C Data
Config Scl = Portb.

6                     'I2C Clock

Wait 1

'работа с микросхемой
I2cstart                                 'даем сигнал старт i2c шине
I2cwbyte &B10100000                      'отправляем адрес микросхемы и адрес блока
I2cwbyte &H00                            'отправляем адрес первой ячейкиI2cwbyte &HF0                            'отправляем 1 байт
I2cwbyte &HF1                            'отправляем 2 байт
I2cwbyte &HF2                            'отправляем 3 байт
I2cwbyte &HF3                            'отправляем 4 байт
I2cwbyte &HF4                            'отправляем 5 байт
I2cwbyte &HF5                            'отправляем 6 байт
I2cwbyte &HF6                            'отправляем 7 байт
I2cwbyte &HF7                            'отправляем 8 байт
I2cwbyte &HF8                            'отправляем 9 байт
I2cwbyte &HF9                            'отправляем 10 байт
I2cwbyte &HFA                            'отправляем 11 байт
I2cwbyte &HFB                            'отправляем 12 байт
I2cwbyte &HFC                            'отправляем 13 байт
I2cwbyte &HFD                            'отправляем 14 байт
I2cwbyte &HFE                            'отправляем 15 байт
I2cwbyte &HFF                            'отправляем 16 байт

I2cstop                                  'останавливаем работу i2c

End

 Здесь записывается вся первая страница числами от 240 (в шестнадцатеричной системе F0) до 255 (FF). 

 Чтение данных

 Теперь разберем как прочитать записанные в микросхему данные. Для чтения одного байта нужно проделать следующее:

  1. Сконфигурировать интерфейс I2C 
  2. Отправить стартовый бит
  3. Отправить адрес микросхемы + адрес блока памяти откуда нужно читать
  4. Отправить адрес ячейки памяти
  5. Снова отправить стартовый бит
  6. Отправить адрес микросхемы и адрес блока памяти с битом «чтение»
  7. Получить байт
  8. Отправить стоповый бит

Чтение первой ячейки первого блока будет выглядеть так:

$regfile = “2313def.dat”
$crystal = 1000000

Dim A As Byte                             'переменная для хранения прочитанного байта

'конфигурируем scl и sda пины
Config Sda = Portb.7                      'I2C Data
Config Scl = Portb.6                      'I2C Clock

Wait 1

'работа с микросхемой

I2cstart                                  'даем сигнал старт i2c шине
I2cwbyte &B10100000                       'отправляем адрес микросхемы и адрес блока
I2cwbyte &H00                             'отправляем адрес ячейки

I2cstart                             'снова отправляем старт
I2cwbyte &B10100001                  'отправляем адрес микросхемы и адрес блока
                                     'на конце посылки теперь будет 1 – бит чтения
I2crbyte A , Nack                    'кладем в переменную А прочитанный байт
                                     'командой Nack прекращаем отправку данных
I2cstop                             'останавливаем работу i2cEnd

 Прочитаный байт записывается в переменную А, командой Nack мы прекращаем отправку микросхемой данных. Если эту команду заменить на Ack, то микросхема продолжит слать данные с ячеек, расположенных следом. Таким образом можно прочитать всю микросхему разом, отправляя команду I2crbyte Variable, Ack необходимое количество раз.

 А теперь практический пример. К микроконтроллеру подключен датчик температуры ds18b20, на дисплей выводится текущая температура и значение температуры, которое было записано в память микросхемы 24LC08. Запись температуры в память микросхемы происходит при нажатии на кнопку PB2, считывание и вывод записанного значения происходит при нажатии на кнопку PB1.

Скачать исходники со статьи одним файлом

 Как видно работать с внешней EEPROM очень даже просто, если будут вопросы, складывайте их в комментариях. Удачи!

Источник: http://AVRproject.ru/publ/kak_podkljuchit/rabota_s_vneshnej_eeprom_v_bascom_avr_na_primere_24lc08/2-1-0-46

Записки программиста

Одна из проблем с микроконтроллерами STM32 заключается в том, что большинство из них не имеют встроенного EEPROM. Исключением являются только микроконтроллеры серий STM32L0 и STM32L1 с ультра низким энергопотреблением. Это довольно странно после работы с AVR, где EEPROM есть у всех микроконтроллеров.

Существует несколько решений, но в рамках этой заметки мы рассмотрим самое очевидное — использование внешнего EEPROM на примере чипа с I2C-интерфейсом 24LC64.<\p>

Дополнение: Альтернативное решение заключается в том, чтобы воспользоваться встроенной backup memory микроконтроллеров STM32.

Подробности по этой теме вы найдете в посте Микроконтроллеры STM32: использование встроенных RTC.

Цифра 64 в названии говорит о том, что устройство имеет 64 килобит памяти, или 8 Кбайт.

Есть аналогичные чипы от разных производителей и с разными объемами памяти — я видел от 128 байт (например, M24C01 от компании ST) до 256 Кбайт (AT24CM02 производства Atmel).

В плане распиновки и интерфейса все они абсолютно взаимозаменяемы. Далее я буду говорить о 24LC64, производимом компанией Microchip, так как сам использовал именно его.

Распиновка 24LC64 выглядит так (даташит [PDF]):

VSS и VCC, понятно, представляют собой минус и плюс питания, а SDA и SCL — это I2C шина. Устройство имеет I2C-адрес 0b1010zyx, где значения x, y и z определяются тем, к чему подключены пины A0, A1 и A2.

Если пин подключен к земле, соответствующий бит адреса равен нулю, если же к плюсу — то единице. Таким образом, устройство может иметь адрес от 0x50 до 0x57. Наконец, пин WP — это write protection. Если пин подключен к земле, разрешено как чтение, так и запись.

Если же пин подключен к плюсу, устройство доступно только на чтение.

Создаем новый проект в STM32CubeMX для вашей отладочной платы. Лично я все также использую плату Nucleo-F411RE, но если вы используете другую, отличия в проекте должны быть минимальными. В Peripherals включаем устройство I2C1, выбрав в выпадающем списке «I2C» вместо «Disable».

Также мы будем передавать что-то компьютеру, поэтому включаем устройство USART2, как делали это в заметке Микроконтроллеры STM32: обмен данными по UART.

Чтобы плату можно было использовать с Arduino-шилдами, несущими какие-то I2C-устройства, пины I2C1 нужно переназначить на PB9 и PB8 (по умолчанию будут назначены другие: PB7 и PB6). В итоге должна получиться такая картинка:

Затем генерируем код и подправляем Makefile, как обычно. Я лично просто взял Makefile из исходников к заметке про UART и дописал недостающие файлы в C_SOURCES, а именно:

$(FIRMWARE)/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c
$(FIRMWARE)/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c_ex.c<\p>

Для подключения 24LC64 к плате Nucleo я воспользовался Proto Shield от Arduino. Пришлось прорезать в нем отверстие Dremel’ем, чтобы иметь доступ к кнопкам на отладочной плате. В итоге у меня получился вот такой сэндвич:

Кто-то из читателей мог обратить внимание на то, что в I2C-шине должны использоваться резисторы, подтягивающие SCL и SDA к плюсу, но в этом проекте мы их не используем. Дело в том, что для моей платы и использованного в ней микроконтроллера STM32CubeMX автоматически включает встроенные подтягивающие резисторы на соответствующих пинах.

Убедиться в этом несложно, посмотрев код процедуры HAL_I2C_MspInit в файле ./Src/stm32f4xx_hal_msp.c:

// …
   

/**I2C1 GPIO Configuration         PB8     ——> I2C1_SCL     PB9     ——> I2C1_SDA

    */

 
    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// …

Правда, не так очевидно, почему процедура HAL_I2C_MspInit вообще откуда-то вызывается. Ответ можно найти в файле stm32f4xx_hal_i2c.c где эта процедура объявляется с атрибутом __weak и вызывается из процедуры HAL_I2C_Init. Атрибут __weak работает таким образом, что при сборке не-weak процедура из кода нашего проекта подменяет собой weak-процедуру из HAL, за счет чего она и будет вызвана.

Заметьте, однако, что встроенные подтягивающие резисторы доступны не во всех микроконтроллерах STM32. Насколько мне известно, для серии STM32F1 это работать не будет, и придется все-таки использовать внешние подтягивающие резисторы.

Читайте также:  Компания atmel расширяет arm cortex-m4-совместимое семейство флеш-микроконтроллеров

Наконец, рассмотрим основной код прошивки:

void init() {
    const char wmsg[] = “Some data”;
    char rmsg[sizeof(wmsg)];
    // HAL expects address to be shifted one bit to the left
    uint16_t devAddr = (0x50

Источник: https://eax.me/stm32-external-eeprom/

AVR: РАБОТАЕМ С ВНЕШНЕЙ ПАМЯТЬЮ I2C EEPROM типа 24CXX

Для того чтобы полностью разобраться с Two-Wire Interface (TWI) , пишем с нуля в AVR STUDIO процедуры инициализации, чтения и записи. Подробно останавливаемся на каждом шаге и разбираемся. Затем промоделируем все в Proteus.

I. Кратко теория

Аппаратный модуль TWI и протокол I2C

В микроконтроллеры серии MEGA входит модуль TWI,  с помощью которого мы будем работать с шиной I2C. Модуль TWI по сути является посредником между нашей программой и подключаемым устройством (память I2C EEPROM, например).

Структура модуля TWI в микроконтроллерах AVR

Шина I2C состоит из двух проводов:

  • SCL (Serial Clock Line) – линия последовательной передачи тактовых импульсов.
  • SDA (Serial Data Line) – линия последовательной передачи данных.

К этой шине мы можем одновременно подключить до 128 микросхем.

Подключения к шине TWI

Наш контроллер будем называть ведущим, а все подключаемые микросхемы ведомыми.

Каждый ведомый имеет определенный адрес, зашитый на заводе в ходе изготовления (кстати, граница в 128 микросхем как раз и определяется диапазоном возможных адресов).

С помощью этого адреса мы и можем работать с каждый ведомым индивидуально, хотя все они подключены к одной шине одинаковым образом. Из нашей программы мы управляем модулем TWI, а этот модуль берет на себя дергание ножками SCL и SDA.

Как видно на блок-схеме, TWI условно состоит из четырех блоков. Вначале нам нужно будет настроить Генератор скорости связи, и мы сразу забудем про него. Основная работа – с Блоком управления.

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

А Блок сравнения адреса нужен, чтобы задать адрес на шине I2C, если тока наш контроллер будет подчиненным устройством(ведомым) от другого какого-то контроллера или будет приемником от другого ведущего (как в статье Налаживаем обмен данными между двумя контроллерами по шине I²C на моем блоге). В этой статье он нам не нужен. Если хотите, почитайте про него в любом даташите контроллера серии MEGA.

Итак, наша задача сейчас разобраться с регистрами, входящими в Генератор скорости связи и Блок управления:

 Регистр скорости связи TWBR

 Регистр управления TWCR

 Регистр статуса(состояния) TWSR

 Регистр данных TWDR

И все, разобравшись всего лишь с 4-мя регистрами, мы сможем полноценно работать с внешней памятью EEPROM и вообще, обмениваться данными по I2C с любым другим устройством.

Генератор скорости связи и Регистр скорости связи TWBR

Блок генератора скорости связи управляет линией SCL, а именно периодом тактовых импульсов. Управлять линией SCL может только ведущий. Период SCL управляется путем установки регистра скорости связи TWI (TWBR) и бит предделителя в регистре статуса TWI (TWSR).

Частота SCL генерируется по следующей формуле:

FSCL= FЦПУ/[16+2(TWBR) · 4TWPS],

где

  • TWBR — значение в регистре скорости связи TWI;
  • TWPS — значение бит предделителя в регистре статуса TWI (TWSR).
  • Fцпу – тактовая частота ведущего
  • FSCL – частота тактовых импульсов линии SCL

Настройка TWBR нужна, т.к. ведомая микросхема обучена обмениваться данными на определенной частоте. Например, в Proteus, введите в поиск I2CMEM, увидите обилие микросхем памяти, в основном у них указаны частоты 100 и 400Khz.

Ну вот, подставляя в формулу частоты FЦПУ и  FSCL, мы сможем найти оптимальное значение для регистра TWBR.

TWPS – это 2-битное число [TWPS1: TWPS0], первый бит – это разряд TWPS0, второй – TWPS1 в регистре статуса TWSR. Задавая Предделитель скорости связи 4TWPS, мы можем понижать значение TWBR (т.к. TWBR – это байт, максимальное значение 255).

Но обычно это не требуется, поэтому TWPS обычно задается 0 и 4TWPS = 1. Гораздо чаще, наоборот, мы упираемся в нижний диапазон регистра TWBR. Если TWI работает в ведущем режиме, то значение TWBR должно быть не менее 10.

Если значение TWBR меньше 10, то ведущее устройство шины может генерировать некорректные сигналы на линиях SDA и SCL во время передачи байта.

TWPS1 TWPS0 TWPS10 Значение предделителя 4TWPS
1
1 1 4
1 2 16
1 1 3 64
Частота ЦПУ, МГц TWBR TWPS Частота SCL, КГц
16.0 12 400
16.0 72 100
14.4 10 400
14.4 64 100
12.0 52 100
8.0 32 100
4.0 12 100
3.6 10 100

Ну вот, настройка TWI в этом и заключается :

 задание значения предделителя ([TWPS1: TWPS0] в регистре статуса TWSR)

Регистр состояния TWI — TWSR

Разряд 7 6 5 4 3 2 1
TWS7 TWS6 TWS5 TWS4 TWS3 TWPS1 TWPS0
Чтение/запись Чт. Чт. Чт. Чт. Чт. Чт. Чт./Зп. Чт./Зп.
Исх. значение 1 1 1 1 1

 задание скорости связи (TWBR, Регистр скорости связи).

Регистр скорости связи шины TWI — TWBR

Разряд 7 6 5 4 3 2 1
TWBR7 TWBR6 TWBR5 TWBR4 TWBR3 TWBR2 TWBR1 TWBR0
Чтение/запись Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп.
Исх. значение

Блок управления

Регистр управления шиной TWI — TWCR

Регистр TWCR предназначен для управления работой TWI. Он используется для разрешения работы TWI, для инициации сеанса связи ведущего путем генерации условия СТАРТ на шине, для генерации подтверждения приема, для генерации условия СТОП и для останова шины во время записи в регистр TWDR. Он также сигнализирует о попытке ошибочной записи в регистр TWDR, когда доступ к нему был запрещен.

Регистр управления шиной TWI — TWCR

Разряд 7 6 5 4 3 2 1
TWINT TWEA TWSTA TWSTO TWWC TWEN TWIE
Чтение/запись Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт. Чт./Зп. Чт. Чт./Зп.
Исх. значение

 Разряд 7 — TWINT: Флаг прерывания TWI

Этот флаг устанавливается аппаратно, если TWI завершает текущее задание (к примеру, передачу, принятие данных) и ожидает реакции программы. Линия SCL остается в низком состоянии, пока установлен флаг TWINT.

Флаг TWINT сбрасывается программно путем записи в него логической 1. Очистка данного флага приводит к возобновлению работы TWI, т.е.

программный сброс данного флага необходимо выполнить после завершения опроса регистров статуса TWSR и данных TWDR.

 Разряд 6 — TWEA: Бит разрешения подтверждения

https://www.youtube.com/watch?v=YVjlczCrK1A

Бит TWEA управляет генерацией импульса подтверждения. Как видно в таблице, по умолчанию он сброшен. Останавливаться на нем не буду, он в данной статье не пригодится.

 Разряд 5 — TWSTA: Бит условия СТАРТ

Мы должны установить данный бит при необходимости стать ведущим на шине I2C. TWI аппаратно проверяет доступность шины и генерирует условие СТАРТ, если шина свободна. Мы проверяем это условие (по регистру статуса, будет далее) и если шина свободна, то мы можем начинать с ней работать. Иначе нужно будет подождать, пока шина освободится.

 Разряд 4 — TWSTO: Бит условия СТОП

Установка бита TWSTO в режиме ведущего приводит к генерации условия СТОП на шине I2C. Если на шине выполняется условие СТОП, то бит TWSTO сбрасывается автоматически и шина освобождается.

 Разряд 3 — TWWC: Флаг ошибочной записи

Бит TWWC устанавливается при попытке записи в регистр данных TWDR, когда TWINT имеет низкий уровень. Флаг сбрасывается при записи регистра TWDR, когда TWINT = 1.

 Разряд 2 — TWEN: Бит разрешения работы TWI

Бит TWEN разрешает работу TWI и активизирует интерфейс TWI. Если бит TWEN установлен, то TWI берет на себя функции управления линиями ввода-вывода SCL и SDA. Если данный бит равен нулю, то TWI отключается и все передачи прекращаются независимо от состояния работы.

 Разряд 1 — Резервный бит

 Разряд 0 — TWIE: Разрешение прерывания TWI

Если в данный бит записана лог. 1 и установлен бит I в регистре SREG (прерывания разрешены глобально), то разрешается прерывание от модуля TWI (ISR (TWI_vect)) при любом изменении регистра статуса.

Регистр состояния TWI – TWSR

Разряд 7 6 5 4 3 2 1
TWS7 TWS6 TWS5 TWS4 TWS3 TWPS1 TWPS0
Чтение/запись Чт. Чт. Чт. Чт. Чт. Чт. Чт./Зп. Чт./Зп.
Исх. значение 1 1 1 1 1

 Разряды 7..3 — TWS: Состояние TWI

Данные 5 бит отражают состояние логики блока TWI и шины I2C. Диапазон кодов состояния 0b0000 0000 – 0b1111 1000, или с 0x00 до 0xF8. Привожу их ниже:

// TWSR values (not bits) // (taken from avr-libc twi.h – thank you Marek Michalkiewicz) // Master (Ведущий) #define TW_START 0x08 //условие СТАРТ #define TW_REP_START 0x10 //условие ПОВТОРНЫЙ СТАРТ (повтор условия начала передачи) // Master Transmitter (Ведущий в роли передающего) #define TW_MT_SLA_ACK 0x18 //Ведущий отправил адрес и бит записи. Ведомый подтвердил свой адрес #define TW_MT_SLA_NACK 0x20 //Ведущий отправил адрес и бит записи. Нет подтверждения приема (ведомый с таким адресом не найден) #define TW_MT_DATA_ACK 0x28 //Ведущий передал данные и ведомый подтвердил прием. #define TW_MT_DATA_NACK 0x30 //Ведущий передал данные. Нет подтверждения приема #define TW_MT_ARB_LOST 0x38 //Потеря приоритета // Master Receiver (Ведущий в роли принимающего) #define TW_MR_ARB_LOST 0x38 //Потеря приоритета #define TW_MR_SLA_ACK 0x40 // Ведущий отправил адрес и бит чтения. Ведомый подтвердил свой адрес #define TW_MR_SLA_NACK 0x48 //Ведущий отправил адрес и бит чтения. Нет подтверждения приема (ведомый с таким адресом не найден) #define TW_MR_DATA_ACK 0x50 //Ведущий принял данные и передал подтверждение #define TW_MR_DATA_NACK 0x58 //Ведущий принял данные и не передал подтверждение // Slave Transmitter (Ведомый в роли передающего) #define TW_ST_SLA_ACK 0xA8 //Получение адреса и бита чтения, возвращение подтверждения #define TW_ST_ARB_LOST_SLA_ACK 0xB0 //Потеря приоритета, получение адреса и бита чтения, возвращение подтверждения #define TW_ST_DATA_ACK 0xB8 //Данные переданы, подтверждение от ведущего принято #define TW_ST_DATA_NACK 0xC0 //Данные переданы. Нет подтверждения приема от ведущего. #define TW_ST_LAST_DATA 0xC8 //Последний переданный байт данных, получение подтверждения // Slave Receiver (Ведомый в роли принимающего) #define TW_SR_SLA_ACK 0x60 //Получение адреса и бита записи, возвращение подтверждения #define TW_SR_ARB_LOST_SLA_ACK 0x68 //Потеря приоритета, получение адреса и бита записи, возвращение подтверждения #define TW_SR_GCALL_ACK 0x70 //Прием общего запроса, возвращение подтверждения #define TW_SR_ARB_LOST_GCALL_ACK 0x78 //Потеря приоритета, прием общего запроса, возвращение подтверждения #define TW_SR_DATA_ACK 0x80 // Прием данных, возвращение подтверждения #define TW_SR_DATA_NACK 0x88 //Данные переданы без подтверждения. #define TW_SR_GCALL_DATA_ACK 0x90 //Прием данных при общем запросе, возвращение подтверждения #define TW_SR_GCALL_DATA_NACK 0x98 // Прием данных при общем запросе, без подтверждения #define TW_SR_STOP 0xA0 //Условие СТОП // Misc (Ошибки интерфейса) #define TW_NO_INFO 0xF8 //Информация о состоянии отсутствует #define TW_BUS_ERROR 0x00 //Ошибка шины

Если мы работаем с пассивным ведомым (т.е. это не другой контроллер AVR, а микросхема памяти или например, часы RTC), то коды состояний из разделов Slave Transmitter (Ведомый в роли передающего) и  Slave Receiver (Ведомый в роли принимающего) нам не понадобятся, поскольку единственная «разумная» микросхема у нас – это Ведущий (наш контроллер).

Проверяя регистр статуса после каждой выполненной операции с шиной, мы можем контролировать обмен информацией. Например, будем точно знать – записались ли данные во внешнюю память или нет.

Регистр данных шины TWI — TWDR

Разряд 7 6 5 4 3 2 1
TWD7 TWD6 TWD5 TWD4 TWD3 TWD2 TWD1 TWD0
Чтение/запись Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп. Чт./Зп.
Исх. значение 1 1 1 1 1 1 1 1

В режиме передатчика регистр TWDR содержит байт для передачи. В режиме приемника регистр TWDR содержит последний принятый байт. Будьте внимательны, после аппаратной установки флага TWINT, регистр TWDR не содержит ничего определенного.

Ну вот, этих знаний более чем достаточно, чтобы работать с I2C EEPROM. Теперь переходим непосредственно к программной части. Я решил пояснения писать прямо в коде в виде комментариев.

II. Программа

Все функции (инициализация TWI, чтение, запись внешней памяти) я вынесу в отдельные файлы, как это и принято делать, i2c_eeprom.c и i2c_eeprom.h.

Содержание i2c_eeprom.h следующее:

#define false 0 #define true 1 //#define slaveF_SCL 100000 //100 Khz #define slaveF_SCL 400000 //400 Khz #define slaveAddressConst 0b1010 //Постоянная часть адреса ведомого устройства #define slaveAddressVar 0b000 //Переменная часть адреса ведомого устройства //Разряды направления передачи данных #define READFLAG 1 //Чтение #define WRITEFLAG 0 //Запись void eeInit(void); //Начальная настройка TWI uint8_t eeWriteByte(uint16_t address,uint8_t data); //Запись байта в модуль памяти EEPROM uint8_t eeReadByte(uint16_t address); //Чтение байта из модуля памяти EEPROM // TWSR values (not bits) // (taken from avr-libc twi.h – thank you Marek Michalkiewicz) // Master #define TW_START 0x08 #define TW_REP_START 0x10 // Master Transmitter #define TW_MT_SLA_ACK 0x18 #define TW_MT_SLA_NACK 0x20 #define TW_MT_DATA_ACK 0x28 #define TW_MT_DATA_NACK 0x30 #define TW_MT_ARB_LOST 0x38 // Master Receiver #define TW_MR_ARB_LOST 0x38 #define TW_MR_SLA_ACK 0x40 #define TW_MR_SLA_NACK 0x48 #define TW_MR_DATA_ACK 0x50 #define TW_MR_DATA_NACK 0x58 // Slave Transmitter #define TW_ST_SLA_ACK 0xA8 #define TW_ST_ARB_LOST_SLA_ACK 0xB0 #define TW_ST_DATA_ACK 0xB8 #define TW_ST_DATA_NACK 0xC0 #define TW_ST_LAST_DATA 0xC8 // Slave Receiver #define TW_SR_SLA_ACK 0x60 #define TW_SR_ARB_LOST_SLA_ACK 0x68 #define TW_SR_GCALL_ACK 0x70 #define TW_SR_ARB_LOST_GCALL_ACK 0x78 #define TW_SR_DATA_ACK 0x80 #define TW_SR_DATA_NACK 0x88 #define TW_SR_GCALL_DATA_ACK 0x90 #define TW_SR_GCALL_DATA_NACK 0x98 #define TW_SR_STOP 0xA0 // Misc #define TW_NO_INFO 0xF8 #define TW_BUS_ERROR 0x00

Чтобы было легче воспринимать нижеследующий код, приведу здесь теоретические примеры осциллограмм при обмене данными между Ведущим и Ведомым по шине I2C (взял в книжке [1], в более высоком разрешении найдете по адресу, указанном в конце статьи):

Далее привожу листинг файла i2c_eeprom.c с подробными комментариями:

#include #include #include “i2c_eeprom.h” void eeInit(void) { /*Настраиваем Генератор скорости связи*/ TWBR = (F_CPU/slaveF_SCL – 16)/(2 * /* TWI_Prescaler= 4^TWPS */1); /* Если TWI работает в ведущем режиме, то значение TWBR должно быть не менее 10. Если значение TWBR меньше 10, то ведущее устройство шины может генерировать некорректные сигналы на линиях SDA и SCL во время передачи байта. */ if(TWBR < 10) TWBR = 10; /* Настройка предделителя в регистре статуса Блока управления. Очищаются биты TWPS0 и TWPS1 регистра статуса, устанавливая тем самым, значение предделителя = 1. */ TWSR &= (~((1

Источник: https://nagits.wordpress.com/2010/12/18/avr_i2c_eeprom/

Ссылка на основную публикацию
Adblock
detector