feat(eeprom-ft24c256): драйвер EEPROM 24Cxx по I2C

Перенесён из KONOR_ds18b20/lib/eeprom; в OpticalTester лежала такая же копия.

Запись режется по границам страниц внутри библиотеки: микросхема
принимает до 64 байт за транзакцию, но при переходе через границу
страницы счётчик адреса заворачивается и затирает уже принятые байты.
Вызывающему коду об этом думать не нужно.
This commit is contained in:
2026-08-23 01:15:12 +03:00
parent 1248a6551a
commit 11bf8b6c11
3 changed files with 720 additions and 0 deletions

View File

@@ -0,0 +1,378 @@
/**
* @file ft24c256.c
* @brief Постраничная запись, сплошное чтение и ожидание готовности FT24C256.
*
* Реализация не хранит буфера всего массива: единственная крупная переменная —
* кадр одной транзакции записи на стеке, длиной адрес слова плюс страница
* (@ref FT24C256_ADDRESS_SIZE + @ref FT24C256_PAGE_MAX байт). Времени
* библиотека не измеряет: выдержку цикла записи даёт порт, а готовность
* определяется откликом самой микросхемы.
*/
#include "ft24c256.h"
/** Кадр записи: адрес слова и страница данных. */
#define FT24C256_FRAME_MAX (FT24C256_ADDRESS_SIZE + FT24C256_PAGE_MAX)
/** Длина куска, которым FT24C256_Update() сверяет содержимое. */
#define FT24C256_COMPARE_MAX 32U
/**
* @brief Проверяет пригодность состояния к обмену.
*
* @param device Состояние кристалла.
* @return 1, если состояние настроено и обратные вызовы на месте, иначе 0.
*/
static uint8_t ft24c256_valid(const FT24C256 *device)
{
if (device == 0) {
return 0U;
}
if (device->ready == 0U) {
return 0U;
}
return (uint8_t)((device->io.write != 0) && (device->io.write_read != 0));
}
/**
* @brief Проверяет, что область целиком помещается в массив.
*
* @param device Состояние кристалла.
* @param address Адрес первого байта.
* @param size Число байтов.
* @return 1, если область допустима, иначе 0.
*/
static uint8_t ft24c256_in_range(const FT24C256 *device, uint32_t address,
uint32_t size)
{
if (address >= device->config.size) {
return 0U;
}
return (uint8_t)(size <= (device->config.size - address));
}
/**
* @brief Раскладывает адрес слова в два байта, старший первым.
*
* @param address Адрес байта в массиве.
* @param frame Приёмник кадра длиной не менее @ref FT24C256_ADDRESS_SIZE.
*/
static void ft24c256_put_address(uint32_t address, uint8_t *frame)
{
frame[0] = (uint8_t)((address >> 8U) & 0xFFU);
frame[1] = (uint8_t)(address & 0xFFU);
}
/**
* @brief Выдерживает паузу, если порт умеет её отсчитывать.
*
* @param device Состояние кристалла.
* @param milliseconds Длительность паузы.
*/
static void ft24c256_delay(FT24C256 *device, uint32_t milliseconds)
{
if (device->io.delay_ms != 0) {
device->io.delay_ms(device->io.context, milliseconds);
}
}
/**
* @brief Записывает часть страницы одной транзакцией и ждёт готовности.
*
* @param device Состояние кристалла.
* @param address Адрес первого байта; не пересекает границу страницы.
* @param data Записываемые данные.
* @param size Число байтов, не больше размера страницы.
* @return 1 при успехе, иначе 0.
*/
static uint8_t ft24c256_write_page(FT24C256 *device, uint32_t address,
const uint8_t *data, uint16_t size)
{
uint8_t frame[FT24C256_FRAME_MAX];
uint16_t index;
if (size > FT24C256_PAGE_MAX) {
return 0U;
}
ft24c256_put_address(address, frame);
for (index = 0U; index < size; index++) {
frame[FT24C256_ADDRESS_SIZE + index] = data[index];
}
if (device->io.write(device->io.context, device->config.address, frame,
(uint16_t)(FT24C256_ADDRESS_SIZE + size)) == 0U) {
device->write_errors++;
return 0U;
}
device->written_bytes += size;
return FT24C256_WaitReady(device);
}
void FT24C256_ConfigDefault(FT24C256_Config *config)
{
if (config == 0) {
return;
}
config->size = FT24C256_SIZE;
config->page_size = FT24C256_PAGE_SIZE;
config->address = FT24C256_BASE_ADDRESS;
config->write_ms = FT24C256_WRITE_MS;
config->poll_retries = FT24C256_POLL_RETRIES;
}
uint8_t FT24C256_Init(FT24C256 *device, const FT24C256_Io *io,
const FT24C256_Config *config)
{
if ((device == 0) || (io == 0)) {
return 0U;
}
if ((io->write == 0) || (io->write_read == 0)) {
return 0U;
}
device->io = *io;
if (config != 0) {
device->config = *config;
} else {
FT24C256_ConfigDefault(&device->config);
}
if (device->config.size == 0U) {
device->config.size = FT24C256_SIZE;
}
if (device->config.page_size == 0U) {
device->config.page_size = FT24C256_PAGE_SIZE;
}
if (device->config.page_size > FT24C256_PAGE_MAX) {
device->config.page_size = FT24C256_PAGE_MAX;
}
if (device->config.address == 0U) {
device->config.address = FT24C256_BASE_ADDRESS;
}
if (device->config.write_ms == 0U) {
device->config.write_ms = FT24C256_WRITE_MS;
}
/* Без опроса готовности единственная мера времени — выдержка порта. */
if ((device->config.poll_retries == 0U) && (device->io.delay_ms == 0)) {
return 0U;
}
device->ready = 1U;
device->written_bytes = 0U;
device->read_bytes = 0U;
device->write_errors = 0U;
device->read_errors = 0U;
return 1U;
}
uint8_t FT24C256_IsPresent(FT24C256 *device)
{
if (ft24c256_valid(device) == 0U) {
return 0U;
}
return device->io.write(device->io.context, device->config.address, 0, 0U);
}
uint8_t FT24C256_WaitReady(FT24C256 *device)
{
uint8_t attempt;
if (ft24c256_valid(device) == 0U) {
return 0U;
}
if (device->config.poll_retries == 0U) {
ft24c256_delay(device, device->config.write_ms);
return 1U;
}
for (attempt = 0U; attempt < device->config.poll_retries; attempt++) {
if (device->io.write(device->io.context, device->config.address, 0,
0U) != 0U) {
return 1U;
}
ft24c256_delay(device, 1U);
}
device->write_errors++;
return 0U;
}
uint8_t FT24C256_Read(FT24C256 *device, uint32_t address, void *buffer,
uint16_t size)
{
uint8_t frame[FT24C256_ADDRESS_SIZE];
if ((ft24c256_valid(device) == 0U) || (buffer == 0)) {
return 0U;
}
if (size == 0U) {
return 1U;
}
if (ft24c256_in_range(device, address, size) == 0U) {
return 0U;
}
ft24c256_put_address(address, frame);
if (device->io.write_read(device->io.context, device->config.address, frame,
FT24C256_ADDRESS_SIZE, (uint8_t *)buffer,
size) == 0U) {
device->read_errors++;
return 0U;
}
device->read_bytes += size;
return 1U;
}
uint8_t FT24C256_Write(FT24C256 *device, uint32_t address, const void *data,
uint16_t size)
{
const uint8_t *source = (const uint8_t *)data;
uint32_t position = address;
uint16_t left = size;
if ((ft24c256_valid(device) == 0U) || (data == 0)) {
return 0U;
}
if (size == 0U) {
return 1U;
}
if (ft24c256_in_range(device, address, size) == 0U) {
return 0U;
}
while (left != 0U) {
const uint32_t page = device->config.page_size;
const uint32_t tail = page - (position % page);
uint16_t chunk = (tail < left) ? (uint16_t)tail : left;
if (ft24c256_write_page(device, position, source, chunk) == 0U) {
return 0U;
}
position += chunk;
source += chunk;
left = (uint16_t)(left - chunk);
}
return 1U;
}
uint8_t FT24C256_Update(FT24C256 *device, uint32_t address, const void *data,
uint16_t size)
{
const uint8_t *source = (const uint8_t *)data;
uint32_t position = address;
uint16_t left = size;
if ((ft24c256_valid(device) == 0U) || (data == 0)) {
return 0U;
}
if (size == 0U) {
return 1U;
}
if (ft24c256_in_range(device, address, size) == 0U) {
return 0U;
}
while (left != 0U) {
uint8_t stored[FT24C256_COMPARE_MAX];
const uint16_t chunk = (left < FT24C256_COMPARE_MAX)
? left
: (uint16_t)FT24C256_COMPARE_MAX;
uint16_t index;
uint8_t differs = 0U;
if (FT24C256_Read(device, position, stored, chunk) == 0U) {
return 0U;
}
for (index = 0U; index < chunk; index++) {
if (stored[index] != source[index]) {
differs = 1U;
break;
}
}
if ((differs != 0U)
&& (FT24C256_Write(device, position, source, chunk) == 0U)) {
return 0U;
}
position += chunk;
source += chunk;
left = (uint16_t)(left - chunk);
}
return 1U;
}
uint8_t FT24C256_ReadByte(FT24C256 *device, uint32_t address, uint8_t *value)
{
if (value == 0) {
return 0U;
}
return FT24C256_Read(device, address, value, 1U);
}
uint8_t FT24C256_WriteByte(FT24C256 *device, uint32_t address, uint8_t value)
{
return FT24C256_Write(device, address, &value, 1U);
}
uint8_t FT24C256_Fill(FT24C256 *device, uint32_t address, uint8_t value,
uint32_t size)
{
uint8_t page[FT24C256_PAGE_MAX];
uint32_t position = address;
uint32_t left = size;
uint16_t index;
if (ft24c256_valid(device) == 0U) {
return 0U;
}
if (size == 0U) {
return 1U;
}
if (ft24c256_in_range(device, address, size) == 0U) {
return 0U;
}
for (index = 0U; index < device->config.page_size; index++) {
page[index] = value;
}
while (left != 0U) {
const uint32_t tail =
device->config.page_size - (position % device->config.page_size);
const uint32_t chunk = (tail < left) ? tail : left;
if (ft24c256_write_page(device, position, page, (uint16_t)chunk) == 0U) {
return 0U;
}
position += chunk;
left -= chunk;
}
return 1U;
}
uint8_t FT24C256_Erase(FT24C256 *device)
{
if (ft24c256_valid(device) == 0U) {
return 0U;
}
return FT24C256_Fill(device, 0U, 0xFFU, device->config.size);
}
uint8_t FT24C256_Protect(FT24C256 *device, uint8_t enabled)
{
if (device == 0) {
return 0U;
}
if (device->io.set_write_protect == 0) {
return 0U;
}
device->io.set_write_protect(device->io.context, (uint8_t)(enabled != 0U));
return 1U;
}
uint32_t FT24C256_Size(const FT24C256 *device)
{
if ((device == 0) || (device->ready == 0U)) {
return 0U;
}
return device->config.size;
}
uint8_t FT24C256_AddressOf(uint8_t index)
{
return (uint8_t)(FT24C256_BASE_ADDRESS + (index & 0x7U));
}