мини-библиотека записи и чтения памяти FRAM FM24

b707
Offline
Зарегистрирован: 26.05.2017

Заказал мне тут один клиент библиотеку чтения и записи чипов памяти FRAM.  Но с условием - не использовать Wire.h, чтобы, значит, для компактности.

Написал. А теперь решил выложить сюда, на ваш суд. Клиент не против.

Основные особенности:

 - маленькая (около 400 байт кода),  для сравнения тоже самое через Wire получается более 2килобайт
-  без внутреннего буфера, что означает возможность записи блока данных произвольной длины в один прием, а не порциями по 32 байта, как через Wire
 - без прерываний
 - возможно изменение частоты шины TWI.

Ошибки отслеживаются в полном обьеме , однако диагностика их минимальна - все функции при ошибке возвращают false. при успехе true.

Библиотека написана только для мк атмега328 и аналогичных. Используются регистры, поэтому на других контроллерах работать не будет. Чипы FRAM поддерживаются от FM24cl02 до FM24cl16, для чипов более 16 мбит алгоритм определения адреса ячейки уже другой.

Код (файл FM24_Mini.h)



/* mini library for reading/writing data from/to FRAM FM24
    (c) board707 2022
    ver 0.01
*/

#ifndef TWI_FREQ
#define TWI_FREQ 100000L    // 100 KHz (max 400 KHz)
#endif

#define USE_INTERNAL_PULLUP 1

// TWI Actions
#define TWI_START           0
#define TWI_RESTART         1
#define TWI_STOP            2
#define TWI_TRANSMIT        3
#define TWI_RECEIVE_ACK     4
#define TWI_RECEIVE_NACK    5

// TWI status codes
#define TW_MT_SLA_ACK     0x18
#define TW_MT_DATA_ACK    0x28
#define TW_MR_SLA_ACK     0x40
#define TW_MR_DATA_ACK    0x50
#define TW_MR_DATA_NACK   0x58

void twi_start(uint32_t twi_freq = TWI_FREQ);

// Twi init
void twi_start(uint32_t twi_freq )
{
#if(USE_INTERNAL_PULLUP)
  // activate internal pullups for twi.
  DDRC  &=  ~ (( 1  <<  4 ) | ( 1  <<  5 )) ;
  PORTC  |=  ( 1  <<  4 ) | ( 1  <<  5 );
#endif

  // set TWI Freq
  TWSR &= ~(_BV(TWPS0) | _BV(TWPS1)); // clear prescaler bits
  TWBR = ((F_CPU / twi_freq) - 16) / 2;
}

// perform TWI action and wait for TWCR change
// return status code
uint8_t twi(uint8_t action)
{
  switch (action)
  {
    case TWI_START:
    case TWI_RESTART:
      TWCR = _BV(TWSTA) | _BV(TWEN) | _BV(TWINT);// Если нужно прерывание | _BV(TWIE);
      break;
    case TWI_STOP:
      TWCR = _BV(TWSTO) | _BV(TWEN) | _BV(TWINT);// | _BV(TWIE);
      break;
    case TWI_TRANSMIT:
      TWCR = _BV(TWEN) | _BV(TWINT);// | _BV(TWIE);
      break;
    case TWI_RECEIVE_ACK:
      TWCR = _BV(TWEN) | _BV(TWINT) | _BV(TWEA);//| _BV(TWIE);
      break;
    case TWI_RECEIVE_NACK:
      TWCR = _BV(TWEN) | _BV(TWINT);// | _BV(TWIE);
      break;
  }
  if (action != TWI_STOP) while (!(TWCR & _BV(TWINT)));
  return (TWSR & 0xF8);
}

// transmit address or data byte via TWI
// return 1 with success, 0 - error
bool transmit_byte_I2C(uint8_t data, uint8_t OK_code)
{
  TWDR = data;
  return (OK_code == twi(TWI_TRANSMIT));
}

// read data from TWI
// return 1 with success, 0 - error
bool receive_byte_I2C(uint8_t* ptr, bool ack)
{ bool res_code = false;

  if (ack) res_code = (TW_MR_DATA_ACK == twi(TWI_RECEIVE_ACK));
  else res_code = (TW_MR_DATA_NACK == twi(TWI_RECEIVE_NACK));
  if (res_code) *ptr = TWDR;
  return res_code;
}


// write data bytes to EEPROM
// disk - 0x50-0x57, startAddress - 0x00 - 0xFF, &data - data ptr, len - (sizeof(data)
// return 1 with success, 0 - error
byte FM24C_write(byte disk, byte startAddress, void *data, unsigned int len) {
  byte* ptr = (byte*) data;
  twi(TWI_START);
  bool twi_OK = false;
  twi_OK = transmit_byte_I2C(disk << 1, TW_MT_SLA_ACK);
  if (twi_OK) twi_OK = transmit_byte_I2C(startAddress, TW_MT_DATA_ACK);

  while (twi_OK && len) {
    twi_OK = transmit_byte_I2C(*ptr, TW_MT_DATA_ACK );
    ptr++;
    len--;
  }

  twi(TWI_STOP);
  return twi_OK;
}


// read data bytes from EEPROM
// disk - 0x50-0x57, startAddress - 0x00 - 0xFF, &data - data ptr, len - (sizeof(data)
// return - number of bytes readed or 0 if error
byte FM24C_read(byte disk, byte startAddress, void *data, unsigned int len) {
  byte rdata = 0;
  byte *p;

  twi(TWI_START);
  bool twi_OK = false;
  twi_OK = transmit_byte_I2C(disk << 1, TW_MT_SLA_ACK);
  if (twi_OK) twi_OK = transmit_byte_I2C(startAddress, TW_MT_DATA_ACK);

  if (twi_OK) {
    twi(TWI_RESTART);
    twi_OK = transmit_byte_I2C(((disk << 1) | 1), TW_MR_SLA_ACK);
    for (rdata = 0, p = (byte*)data; (twi_OK && rdata < len); rdata++, p++) {

      twi_OK = receive_byte_I2C(p, rdata < (len - 1));

    }
  }
  twi(TWI_STOP);
  return (rdata);
}

пример использования

#define disk1 0x50   //страницы FM24C
#include "FM24_Mini.h"

void setup() {
// setup TWI bus  
   twi_start();              // use default TWI freq = 100 KHz
// twi_start(400000L);       // alternative: setup TWI freq 400 KHz

  bool result = false;
  Serial.begin(9600);
  
  // пишем данные
  Serial.print("writing...");
  
  // Writing ulong (uint32_t)
  unsigned long aa = 12345678;
  // write data to disk1 (0x50), addr 254, from variable &aa, size of data 4 bytes (sizeof(uint32_t))
  result = FM24C_write(disk1, 254, &aa, sizeof(aa));
  if (result) Serial.println("write OK!");
  else Serial.println("write error!");

  // Writing int (uint16_t)
  // int a = -12345;
  // result = FM24C_write(disk1, 0, &a, sizeof(a));
  
  // читаем данные
  Serial.print("reading...");
  uint32_t b;
  // read data from disk1 (0x50), addr 254, to variable &b, size of data 4 bytes (sizeof(uint32_t))
  result = FM24C_read(disk1, 254, &b, sizeof(b));
  if (result) {Serial.println("read OK!");
   Serial.print("Value read: ");
   Serial.println(b);
  }
  else Serial.println("read error!");
  
}

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

 

b707
Offline
Зарегистрирован: 26.05.2017

Поправка - поддерживаются чипы Фрам FM24 от 2х до 16 Кбит, а не Мбит, как сказано в первом сообщении - это опечатка.

Дисклеймер - код не писался с нуля, использованы обрывки кода разных авторов.