FRAM НА ОСНОВЕ МИКРОСХЕМЫ FM24C04B-GTR

 

Код программы довольно громоздкий и при интеграции его в большой проект могут возникнуть сложности как с собственным пониманием чего к чему, так и при обращении к ИИ. Они ведь длинных "телег" пока что не понимают. Решил сделать что-то типа библиотеки, где предусмотрена возможность задать пин, порт и т. п. Кстати, довольно много времени потратил на решение этой задачи, даже с помощью ИИ. И до сих пор нахожусь в некоторой растерянности, в недоумении, почему не получалось. Больше всего запар оказалось с реализацией эхо в UART. Прикол в том, что передача работала корректно, а прием почему-то подпездывал... Я встречал подобную проблему с реализацией софтрварного UART, тогда пришлось делать разные BIT_DELAY для приема и передачи. Но тут все же хардварный вариант... И прием данных все-таки работал нормально, а значит с таймингами должно было быть все ок. Было предположение, что проблема в кварцевом резонаторе. Но я так это и не выяснил. В итоге чот с горя там намутил с фьюзами на тестовой плате и она "окирпичилась". Слава Богу (Бога нет, но вы держитесь), у меня есть привычка готовить сразу 2 прототипа. И вот, на втором проблемы эхо просто тупо не возникло! Что ж, ладно... На окирпиченной позже заменю кварц и припаяю 168-ю атмегу. Несмотря на некоторые технические недочеты, плата еще поработает и пригодится. Итак, сначала глянем на выжимку из даташита, чтобы понять, о чем, собственно, речь:

Figure 1. 8-pin SOIC pinout

FM24C04B GTR 1

Описание выводов
Имя выводаТипОписание
A2–A1 Вход Биты выбора адреса (2–1): используются для выбора одного из до 4-х устройств одного типа на общей I²C-шине. Чтобы выбрать устройство, значение адреса на этих пинах должно совпадать с соответствующими битами в адресе ведомого. Входы подтянуты к «0» внутри микросхемы.
SDA Вход/Выход Серийные данные/адрес (I²C): двунаправленный пин интерфейса I²C. Открытый коллектор, требует внешней подтяжки к питанию. Вход имеет шумозащиту на основе триггера Шмитта, выход — ограничение скорости фронта для уменьшения помех.
SCL Вход Серийные часы (I²C): тактовый пин интерфейса I²C. Данные считываются на восходящем фронте, выводятся на нисходящем. Также имеет вход с триггером Шмитта для подавления шумов.
WP Вход Защита от записи: при подключении к VDD вся память становится защищённой от записи. При подключении к земле — запись разрешена. Внутри подтянут к «0».
VSS Питание Общий (земля) устройства. Должен быть соединён с землёй системы.
VDD Питание Питание устройства (положительное напряжение).

 

FM24C04B GTR 2 

Рекомендованные номиналы подтягивающих резисторов для I²C
Напряжение питанияЧастота шины I²CРезистор подтяжки (на каждую линию SDA/SCL)
5 В До 100 кГц 4.7 кОм
5 В До 400 кГц 2.2 – 4.7 кОм
3.3 В До 100 кГц 4.7 – 10 кОм
3.3 В До 400 кГц 2.2 – 4.7 кОм
1.8 В До 400 кГц 1.8 – 3.3 кОм

 

📦 Библиотека fram.h / fram.c

Прямое управление FM24C04 (I²C FRAM) через програмную шину
Поддерживаемые микроконтроллеры: AVR (ATmega)


🧠 Общее описание

Библиотека fram.h/.c реализует низкоуровневый драйвер для FM24C04 (неэвмонесткая память на шине I²C), без использования аппаратного TWI. Используются любые GPIO (напр., PD7 для SDA, PD5 для SCL), с поддержкой переназначения.


⚙️ Аппаратная схема подключения

  • FM24C04 по шине I²C:

    • SCL — к PD5

    • SDA — к PD7

    • VCC — 3.3В или 5В

    • WP — GND (разрешена запись)

    • GND — земля

  • Нужны pull-up резисторы (2.2ком–10ком) на SDA и SCL.


📌 Конфигурация

В main.c или fram_config.h:

#define FRAM_SDA_PORT PORTD
#define FRAM_SDA_DDR  DDRD
#define FRAM_SDA_PINR PIND
#define FRAM_SDA_BIT  PD7

#define FRAM_SCL_PORT PORTD
#define FRAM_SCL_DDR  DDRD
#define FRAM_SCL_PINR PIND
#define FRAM_SCL_BIT  PD5

📚 Доступные функции

void fram_write(uint16_t addr, uint8_t byte);

Запись 1 байта в память по адресу addr (0..511)

uint8_t fram_read(uint16_t addr);

Чтение 1 байта из памяти

void fram_fill(uint8_t value);

Заполняет всю память значением value

void fram_dump(uint16_t start, uint16_t len, void (*print_byte)(uint8_t));

Чтение блока памяти c выводом через функцию

bool fram_test(uint16_t addr, uint8_t value);

Тестовая запись/чтение: true, если данные совпали


📝 Работа со строками

void fram_write_str(uint16_t addr, const char* str);

Запись C-строки (до \0), по адресу addr

void fram_read_str(uint16_t addr, char* buf, uint16_t maxlen);

Чтение C-строки в буфер buf, не более maxlen


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

Запись строки с UART:

if (uart_ready) {
    fram_write_str(0x00, uart_last_message);
    uart_ready = false;
}

Чтение и вывод:

char tmp[64];
fram_read_str(0x00, tmp, sizeof(tmp));
uart_print(tmp);

⏳ Время и надёжность

  • Запись: ~50–100 мкс на байт

  • Безограничный ресурс записи (FRAM!)

  • Не требует завершающих пауз, как EEPROM


🤖 Возможности расширения

  • Хранение настроек пользователя

  • Журналы/логи для ошибок

  • Емуляция EEPROM с постоянной записью


📁 Структура проекта

main.c
fram.h
fram.c

Готово для интеграции в системы с UART, CLI, сохранением статусов, паролей и др.

Исходный код:

/* * fram.h * * Created: 20.05.2025 9:50:01 * Author: madmentat */
#define F_CPU 16000000UL
  #ifndef FRAM_H #define FRAM_H   #include <stdint.h> #include <stdbool.h> #include <util/delay.h> #include <avr/io.h>   #ifndef FRAM_SDA_PORT #define FRAM_SDA_PORT PORTD #endif #ifndef FRAM_SDA_DDR #define FRAM_SDA_DDR DDRD #endif #ifndef FRAM_SDA_PINR #define FRAM_SDA_PINR PIND #endif #ifndef FRAM_SDA_BIT #define FRAM_SDA_BIT PD7 #endif   #ifndef FRAM_SCL_PORT #define FRAM_SCL_PORT PORTD #endif #ifndef FRAM_SCL_DDR #define FRAM_SCL_DDR DDRD #endif #ifndef FRAM_SCL_PINR #define FRAM_SCL_PINR PIND #endif #ifndef FRAM_SCL_BIT #define FRAM_SCL_BIT PD5 #endif   void fram_write(uint16_t addr, uint8_t value); uint8_t fram_read(uint16_t addr); void fram_fill(uint8_t value); void fram_dump(uint16_t start, uint16_t len, void (*print_byte)(uint8_t)); bool fram_test(uint16_t addr, uint8_t value);   #endif  
/* * fram.c * * Created: 20.05.2025 9:50:01 * Author: madmentat */
#include "fram.h"
 
#define SDA_HIGH() (FRAM_SDA_DDR &= ~(1 << FRAM_SDA_BIT))
#define SDA_LOW()  do { FRAM_SDA_DDR |= (1 << FRAM_SDA_BIT); FRAM_SDA_PORT &= ~(1 << FRAM_SDA_BIT); } while (0)
#define SDA_READ   (FRAM_SDA_PINR & (1 << FRAM_SDA_BIT))
 
#define SCL_HIGH() (FRAM_SCL_DDR &= ~(1 << FRAM_SCL_BIT))
#define SCL_LOW()  do { FRAM_SCL_DDR |= (1 << FRAM_SCL_BIT); FRAM_SCL_PORT &= ~(1 << FRAM_SCL_BIT); } while (0)
 
#define I2C_DELAY() _delay_us(5)
#define FRAM_I2C_ADDR 0xA0 // Базовый адрес устройства (0x50 << 1)
 
static void i2c_start(void) {
    SDA_HIGH(); SCL_HIGH(); I2C_DELAY();
    SDA_LOW(); I2C_DELAY();
    SCL_LOW(); I2C_DELAY();
}
 
static void i2c_stop(void) {
    SDA_LOW(); I2C_DELAY();
    SCL_HIGH(); I2C_DELAY();
    SDA_HIGH(); I2C_DELAY();
}
 
static bool i2c_write(uint8_t data) {
    for (uint8_t i = 0; i < 8; i++) {
        if (data & 0x80) SDA_HIGH(); else SDA_LOW();
        I2C_DELAY();
        SCL_HIGH(); I2C_DELAY();
        SCL_LOW(); I2C_DELAY();
        data <<= 1;
    }
    SDA_HIGH();
    I2C_DELAY();
    SCL_HIGH(); I2C_DELAY();
    bool ack = !(SDA_READ);
    SCL_LOW(); I2C_DELAY();
    return ack;
}
 
static uint8_t i2c_read(bool ack) {
    uint8_t data = 0;
    SDA_HIGH();
    for (uint8_t i = 0; i < 8; i++) {
        data <<= 1;
        SCL_HIGH(); I2C_DELAY();
        if (SDA_READ) data |= 1;
        SCL_LOW(); I2C_DELAY();
    }
    if (ack) SDA_LOW(); else SDA_HIGH();
    SCL_HIGH(); I2C_DELAY();
    SCL_LOW(); I2C_DELAY();
    SDA_HIGH();
    return data;
}
 
void fram_write(uint16_t addr, uint8_t value) {
    uint8_t device_addr = FRAM_I2C_ADDR | (((addr >> 8) & 0x01) << 1); // Учитываем бит 9 адреса памяти
    i2c_start();
    if (!i2c_write(device_addr)) {
        i2c_stop();
        return; // Если нет ACK, прерываем
    }
    if (!i2c_write((uint8_t)addr)) {
        i2c_stop();
        return;
    }
    if (!i2c_write(value)) {
        i2c_stop();
        return;
    }
    i2c_stop();
    _delay_ms(5); // Задержка для завершения записи
}
 
uint8_t fram_read(uint16_t addr) {
    uint8_t device_addr = FRAM_I2C_ADDR | (((addr >> 8) & 0x01) << 1);
    i2c_start();
    if (!i2c_write(device_addr)) {
        i2c_stop();
        return 0xFF; // Если нет ACK, возвращаем FF
    }
    if (!i2c_write((uint8_t)addr)) {
        i2c_stop();
        return 0xFF;
    }
    i2c_start();
    if (!i2c_write(device_addr | 0x01)) {
        i2c_stop();
        return 0xFF;
    }
    uint8_t val = i2c_read(false);
    i2c_stop();
    return val;
}
 
void fram_fill(uint8_t value) {
    for (uint16_t addr = 0; addr < 512; addr++) {
        fram_write(addr, value);
    }
}
 
void fram_dump(uint16_t start, uint16_t len, void (*print_byte)(uint8_t)) {
    for (uint16_t i = 0; i < len; i++) {
        print_byte(fram_read(start + i));
    }
}
 
bool fram_test(uint16_t addr, uint8_t value) {
    fram_write(addr, value);
    return (fram_read(addr) == value);
}
/*
 * main.c
 *
 * Created: 20.05.2025 9:50:01
 *  Author: madmentat
 */
#define F_CPU 16000000UL
#include<avr/io.h>
#include<util/delay.h>
#include<avr/interrupt.h>
#include<string.h>
#include<stdbool.h>
 
#define FRAM_SDA_PORT PORTD
#define FRAM_SDA_DDR DDRD
#define FRAM_SDA_PINR PIND
#define FRAM_SDA_BIT PD7
#define FRAM_SCL_PORT PORTD
#define FRAM_SCL_DDR DDRD
#define FRAM_SCL_PINR PIND
#define FRAM_SCL_BIT PD5
#ifndef BTN_WRITE
#define BTN_WRITE PC5
#endif
#ifndef BTN_READ
#define BTN_READ PB2
#endif
#ifndef BTN_CLEAR
#define BTN_CLEAR PD2
#endif
#include"fram.h"
#define BAUD 9600
#define MYUBRR 103
#define MAX_INPUT_LENGTH 32
#define TIMEOUT_MS 100
 
char uart_buffer[MAX_INPUT_LENGTH];
char uart_last_message[MAX_INPUT_LENGTH];
volatile uint8_t uart_index=0;
volatile bool uart_ready=false;
volatile bool uart_terminated=false;
volatile uint16_t timer_counter=0;
volatile bool timer_active=false;
 
// Буфер для отладки ошибок
char debug_buffer[64];
volatile uint8_t debug_index=0;
 
void uart_init(unsigned int ubrr){
    UCSR0A=0;
    UBRR0H=(unsigned char)(ubrr>>8);
    UBRR0L=(unsigned char)ubrr;
    UCSR0B=(1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0);
    UCSR0C=(1<<UCSZ01)|(1<<UCSZ00);
    TCCR0A = 0;
    TCCR0B = (1<<CS02)|(1<<CS00);
    TIMSK0 = (1<<TOIE0);
    sei();
}
 
void uart_tx(char data){
    while(!(UCSR0A&(1<<UDRE0)));
    UDR0=data;
}
 
void uart_print(const char*s){
    while(*s){
        if(*s=='\n') uart_tx('\r');
        uart_tx(*s++);
    }
}
 
void uart_print_hex(uint8_t byte){
    const char*hex="0123456789ABCDEF";
    uart_tx(hex[(byte>>4)&0x0F]);
    uart_tx(hex[byte&0x0F]);
}
 
ISR(TIMER0_OVF_vect){
    if(timer_active){
        timer_counter++;
        if(timer_counter >= 6){
            if(uart_index > 0){
                uart_buffer[uart_index]='\0';
                strcpy(uart_last_message,uart_buffer);
                uart_ready=true;
                uart_terminated=true;
                uart_index=0;
                if(debug_index < sizeof(debug_buffer)-10){
                    strcpy(debug_buffer + debug_index, "[TIMEOUT]\r\n");
                    debug_index += strlen("[TIMEOUT]\r\n");
                }
            }
            timer_active=false;
            timer_counter=0;
        }
    }
}
 
ISR(USART_RX_vect){
    char c=UDR0;
    if(UCSR0A & ((1<<FE0)|(1<<DOR0)|(1<<UPE0))){
        if(debug_index < sizeof(debug_buffer)-20){
            strcpy(debug_buffer + debug_index, "[ERROR:");
            debug_index += 7;
            if(UCSR0A & (1<<FE0)) { strcpy(debug_buffer + debug_index, "FE"); debug_index += 2; }
            if(UCSR0A & (1<<DOR0)) { strcpy(debug_buffer + debug_index, "DOR"); debug_index += 3; }
            if(UCSR0A & (1<<UPE0)) { strcpy(debug_buffer + debug_index, "UPE"); debug_index += 3; }
            strcpy(debug_buffer + debug_index, "]\r\n");
            debug_index += 2;
        }
    }
    if(debug_index < sizeof(debug_buffer)-10){
        const char* hex = "0123456789ABCDEF";
        strcpy(debug_buffer + debug_index, "[RX:");
        debug_index += 4;
        debug_buffer[debug_index++] = hex[(c>>4)&0x0F];
        debug_buffer[debug_index++] = hex[c&0x0F];
        debug_buffer[debug_index++] = ']';
    }
    if((c >= 0x20 && c <= 0x7E) || c == '\r' || c == '\n'){
        if(c=='\r'||c=='\n'){
            uart_buffer[uart_index]='\0';
            strcpy(uart_last_message,uart_buffer);
            uart_ready=true;
            uart_terminated=true;
            uart_index=0;
            if(debug_index < sizeof(debug_buffer)-10){
                const char* hex = "0123456789ABCDEF";
                strcpy(debug_buffer + debug_index, "[END:");
                debug_index += 5;
                debug_buffer[debug_index++] = hex[(c>>4)&0x0F];
                debug_buffer[debug_index++] = hex[c&0x0F];
                strcpy(debug_buffer + debug_index, "]\r\n");
                debug_index += 3;
            }
            timer_active=false;
            timer_counter=0;
            }else{
            if(uart_index<MAX_INPUT_LENGTH-1){
                uart_buffer[uart_index++]=c;
                timer_counter=0;
                timer_active=true;
                if(uart_index >= MAX_INPUT_LENGTH-1){
                    uart_buffer[uart_index]='\0';
                    strcpy(uart_last_message,uart_buffer);
                    uart_ready=true;
                    uart_terminated=true;
                    uart_index=0;
                    if(debug_index < sizeof(debug_buffer)-10){
                        strcpy(debug_buffer + debug_index, "[MAX]\r\n");
                        debug_index += strlen("[MAX]\r\n");
                    }
                    timer_active=false;
                    timer_counter=0;
                }
            }
        }
        }else{
        if(debug_index < sizeof(debug_buffer)-10){
            strcpy(debug_buffer + debug_index, "[DROP]\r\n");
            debug_index += strlen("[DROP]\r\n");
        }
    }
}
 
void io_init(){
    DDRC&=~(1<<BTN_WRITE);
    DDRB&=~(1<<BTN_READ);
    DDRD&=~(1<<BTN_CLEAR);
    PORTC|=(1<<BTN_WRITE);
    PORTB|=(1<<BTN_READ);
    PORTD|=(1<<BTN_CLEAR);
}
 
bool btn_pressed(uint8_t pin,volatile uint8_t*pinr){
    return !(*pinr&(1<<pin));
}
 
void fram_write_str(uint16_t addr, const char* str){
    uint16_t max_addr = 0x200;
    uint16_t i = 0;
    uint16_t last_addr = addr;
    while(str[i] && addr < max_addr){
        fram_write(addr++, str[i++]);
        last_addr = addr;
    }
    if(last_addr < max_addr){
        fram_write(last_addr, 0);
        }else{
        fram_write(max_addr - 1, 0);
    }
    // Отладка: проверяем, что записалось
    if(debug_index < sizeof(debug_buffer)-30){
        strcpy(debug_buffer + debug_index, "[FRAM_WRITE_DEBUG:");
        debug_index += 18;
        for(uint8_t j = 0; j < (i < 8 ? i : 8) && debug_index < sizeof(debug_buffer)-5; j++){
            const char* hex = "0123456789ABCDEF";
            uint8_t byte = fram_read(j); // Читаем напрямую для проверки
            debug_buffer[debug_index++] = hex[(byte >> 4) & 0x0F];
            debug_buffer[debug_index++] = hex[byte & 0x0F];
            if(j < 7 && debug_index < sizeof(debug_buffer)-2) debug_buffer[debug_index++] = ' ';
        }
        strcpy(debug_buffer + debug_index, "]\r\n");
        debug_index += 3;
    }
}
 
void fram_read_str(uint16_t addr, char* buf, uint16_t maxlen){
    uint16_t i = 0;
    uint16_t start_addr = addr;
    for(i = 0; i < maxlen-1 && addr < 0x200; i++){
        char c = fram_read(addr++);
        buf[i] = c;
        if(c == 0) break;
    }
    buf[i] = '\0';
    if(debug_index < sizeof(debug_buffer)-30 && i > 0){
        strcpy(debug_buffer + debug_index, "[FRAM_READ_DEBUG:");
        debug_index += 17;
        for(uint8_t j = 0; j < (i < 8 ? i : 8) && debug_index < sizeof(debug_buffer)-5; j++){
            const char* hex = "0123456789ABCDEF";
            debug_buffer[debug_index++] = hex[(fram_read(start_addr + j) >> 4) & 0x0F];
            debug_buffer[debug_index++] = hex[fram_read(start_addr + j) & 0x0F];
            if(j < 7 && debug_index < sizeof(debug_buffer)-2) debug_buffer[debug_index++] = ' ';
        }
        strcpy(debug_buffer + debug_index, "]\r\n");
        debug_index += 3;
    }
}
 
int main(void){
    uart_init(MYUBRR);
    io_init();
    for(uint16_t a=0; a<0x200; a++) fram_write(a, 0);
    uart_print("FRAM UART interface ready\n");
    while(1){
        if(debug_index > 0){
            debug_buffer[debug_index] = '\0';
            uart_print(debug_buffer);
            debug_index = 0;
        }
        if(uart_ready){
            uart_print("Echo: ");
            uart_print(uart_last_message);
            uart_print("\r\n");
            uart_ready=false;
        }
        if(btn_pressed(BTN_CLEAR,&PIND)){
            uart_print("[BTN_CLEAR] Clearing FRAM...\n");
            for(uint16_t a=0; a<0x200; a++) fram_write(a, 0);
            uart_print("[BTN_CLEAR] FRAM cleared.\n");
            _delay_ms(500);
            while(btn_pressed(BTN_CLEAR,&PIND));
            _delay_ms(50);
        }
        if(btn_pressed(BTN_WRITE,&PINC)){
            _delay_ms(30);
            if(btn_pressed(BTN_WRITE,&PINC)){
                uart_print("[BTN_WRITE] uart_ready=");
                uart_print(uart_ready ? "true" : "false");
                uart_print(" last_message=");
                uart_print(uart_last_message);
                uart_print("\r\n");
                if(uart_terminated && uart_last_message[0] != '\0'){
                    uart_print("[BTN_WRITE] Writing to FRAM: ");
                    uart_print(uart_last_message);
                    uart_print("\r\n");
                    fram_write_str(0x00, uart_last_message);
                    uart_print("[BTN_WRITE] Data written successfully.\r\n");
                    uart_terminated=false;
                    }else{
                    uart_print("[BTN_WRITE] Nothing received yet\r\n");
                }
                _delay_ms(500);
                while(btn_pressed(BTN_WRITE,&PINC));
                _delay_ms(50);
            }
        }
        if(btn_pressed(BTN_READ,&PINB)){
            uart_print("[BTN_READ] Reading from FRAM:\r\n");
            char tmp[64];
            fram_read_str(0x00, tmp, sizeof(tmp));
            uart_print(tmp);
            uart_print("\r\n[BTN_READ] Read complete.\r\n");
            _delay_ms(500);
            while(btn_pressed(BTN_READ,&PINB));
            _delay_ms(50);
        }
    }
}

  fram 1