FRAM НА ОСНОВЕ МИКРОСХЕМЫ FM24C04B-GTR
Код программы довольно громоздкий и при интеграции его в большой проект могут возникнуть сложности как с собственным пониманием чего к чему, так и при обращении к ИИ. Они ведь длинных "телег" пока что не понимают. Решил сделать что-то типа библиотеки, где предусмотрена возможность задать пин, порт и т. п. Кстати, довольно много времени потратил на решение этой задачи, даже с помощью ИИ. И до сих пор нахожусь в некоторой растерянности, в недоумении, почему не получалось. Больше всего запар оказалось с реализацией эхо в UART. Прикол в том, что передача работала корректно, а прием почему-то подпездывал... Я встречал подобную проблему с реализацией софтрварного UART, тогда пришлось делать разные BIT_DELAY для приема и передачи. Но тут все же хардварный вариант... И прием данных все-таки работал нормально, а значит с таймингами должно было быть все ок. Было предположение, что проблема в кварцевом резонаторе. Но я так это и не выяснил. В итоге чот с горя там намутил с фьюзами на тестовой плате и она "окирпичилась". Слава Богу (Бога нет, но вы держитесь), у меня есть привычка готовить сразу 2 прототипа. И вот, на втором проблемы эхо просто тупо не возникло! Что ж, ладно... На окирпиченной позже заменю кварц и припаяю 168-ю атмегу. Несмотря на некоторые технические недочеты, плата еще поработает и пригодится. Итак, сначала глянем на выжимку из даташита, чтобы понять, о чем, собственно, речь:
Figure 1. 8-pin SOIC pinout
Описание выводов | ||
---|---|---|
Имя вывода | Тип | Описание |
A2–A1 | Вход | Биты выбора адреса (2–1): используются для выбора одного из до 4-х устройств одного типа на общей I²C-шине. Чтобы выбрать устройство, значение адреса на этих пинах должно совпадать с соответствующими битами в адресе ведомого. Входы подтянуты к «0» внутри микросхемы. |
SDA | Вход/Выход | Серийные данные/адрес (I²C): двунаправленный пин интерфейса I²C. Открытый коллектор, требует внешней подтяжки к питанию. Вход имеет шумозащиту на основе триггера Шмитта, выход — ограничение скорости фронта для уменьшения помех. |
SCL | Вход | Серийные часы (I²C): тактовый пин интерфейса I²C. Данные считываются на восходящем фронте, выводятся на нисходящем. Также имеет вход с триггером Шмитта для подавления шумов. |
WP | Вход | Защита от записи: при подключении к VDD вся память становится защищённой от записи. При подключении к земле — запись разрешена. Внутри подтянут к «0». |
VSS | Питание | Общий (земля) устройства. Должен быть соединён с землёй системы. |
VDD | Питание | Питание устройства (положительное напряжение). |
Рекомендованные номиналы подтягивающих резисторов для 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); } } }